Complete Pygame Project
Basic Movement & Key Presses
youtube
What is Pygame?
Pygame is a third party module designed for creating 2D games with python.
It has very simple and intuitive syntax that can allows us to create powerful games quickly.
Pygame uses surfaces for drawing.
This means whenever we want to draw something to the screen it must be converted to a surface.
There are many methods and functions within pygame that can do this easily for us.
Installing Pygame
Installing pygame can be as easy as:
1. Opening your command prompt
2. Typing pip install pygame
However, in many cases this will not work.
Windows: How to Install Pygame on Windows
Creating a Window
After we import pygame it is a good idea to initialize it, like so:
import pygame
pygame.init()
Once we've done that we need to setup a windows that will represent our game.
import pygame
pygame.init()
win = pygame.display.set_mode((500, 500))
# This line creates a window of 500 width, 500 height
Now when we run the program we get something that looks like this.
This is great but we'd like to give our game a more creative name than "pygame window".
To do this we can can type the following.
pygame.display.set_caption("First Game")
That's better!
Moving a Character
We are going to start by defining a few variables to represent our character.
x = 50
y = 50
width = 40
height = 60
vel = 5
Now we are going to setup our main-loop or game-loop.
All games have some sort of loop that executes constantly.
This loop is responsible for tasks such as checking for events (such as keyboard events or collision), moving objects, updating the display and eventually ending the game.
In our game we will use a while loop.
Inside the loop we will implement a time delay so we can control the speed of the game.
We will also start by checking for some specific events.
import pygame
pygame.init()
win = pygame.display.set_mode((500,500))
pygame.display.set_caption("First Game")
x = 50
y = 50
width = 40
height = 60
vel = 5
run = True
while run:
pygame.time.delay(100) # delay the game 0.1 seconds
for event in pygame.event.get(): # loop through list of events.
if event.type == pygame.QUIT: # Checks if button QUIT clicked
run = False # Ends the game loop
pygame.quit # If we exit the loop this will execute and close our game
Now we can draw a rectangle to the screen to represent our character.
We will draw the rectangle in the main loop so that it is constantly redrawn each time we loop.
import pygame
pygame.init()
win = pygame.display.set_mode((500,500))
pygame.display.set_caption("First Game")
x = 50
y = 50
width = 40
height = 60
vel = 5
run = True
while run:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
# window/surface, color, rect
pygame.draw.rect(win, (255,0,0), (x, y, width, height))
pygame.display.update() # updates the screen to see rectangle
pygame.quit
Now we can start checking for events so that we can move our character.
import pygame
pygame.init()
win = pygame.display.set_mode((500,500))
pygame.display.set_caption("First Game")
x = 50
y = 50
width = 40
height = 60
vel = 5
run = True
while run:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
# a dictonary each key has a value of 1 or 0.
# Where 1 is pressed and 0 is not pressed.
keys = pygame.key.get_pressed()
if keys[]pygame.K_LEFT]: # We can check if a key is pressed like this
if keys[]pygame.K_RIGHT]:
if keys[]pygame.K_UP]:
if keys[]pygame.K_DOWN]:
pygame.draw.rect(win, (255,0,0), (x, y, width, height))
pygame.display.update()
pygame.quit
Inside the if statements we will change the value of the variables x and y to move the character.
This brings me to the coordinate system.
In pygame the top left corner of the screen is (0,0) and the bottom right is (width, height).
This means to move up we subtract from the y of our character and to move down we add to the y.
import pygame
pygame.init()
win = pygame.display.set_mode((500,500))
pygame.display.set_caption("First Game")
x = 50
y = 50
width = 40
height = 60
vel = 5
run = True
while run:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[]pygame.K_LEFT]:
x -= vel
if keys[]pygame.K_RIGHT]:
x += vel
if keys[]pygame.K_UP]:
y -= vel
if keys[]pygame.K_DOWN]:
y += vel
pygame.draw.rect(win, (255,0,0), (x, y, width, height))
pygame.display.update()
pygame.quit
Now when we run the program and start moving our rectangle we can still see all of the previous rectangles.
To fix this we simply have to draw over the previous shape before redrawing another one.
We can use window.fill(color) to do this.
import pygame
pygame.init()
win = pygame.display.set_mode((500,500))
pygame.display.set_caption("First Game")
x = 50
y = 50
width = 40
height = 60
vel = 5
run = True
while run:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[]pygame.K_LEFT]:
x -= vel
if keys[]pygame.K_RIGHT]:
x += vel
if keys[]pygame.K_UP]:
y -= vel
if keys[]pygame.K_DOWN]:
y += vel
win.fill((0,0,0)) # Fills the screen with black
pygame.draw.rect(win, (255,0,0), (x, y, width, height))
pygame.display.update()
pygame.quit
And now we can move our character around!
Jumping & Boundaries
youtube
Setting Boundaries
In the last tutorial we created a rectangle that we could move around the screen using the arrow keys.
However, when we reach the end of the screen the rectangle is still allowed to continue to move.
To stop this we need to set up some boundaries and check that our rectangle is within them before moving it again.
To do this we can simply check the x and y coordinates of the rectangle against the dimensions of the screen.
When we do this we need to remember that when we draw something in pygame we draw from the top left hand corner of the object.
This means the top left corner will be our x and y values while the top right corner will be (x + width, y) and the bottom left will be (x, y + height).
import pygame
pygame.init()
win = pygame.display.set_mode((500,500))
pygame.display.set_caption("First Game")
x = 50
y = 50
width = 40
height = 60
vel = 5
run = True
while run:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
# Making sure the top left position of our character is greater than our vel so we never move off the screen.
if keys[]pygame.K_LEFT] and x > vel:
x -= vel
# Making sure the top right corner of our character is less than the screen width - its width
if keys[]pygame.K_RIGHT] and x < 500 - vel - width:
x += vel
if keys[]pygame.K_UP] and y > vel: # Same principles apply for the y coordinate
y -= vel
if keys[]pygame.K_DOWN] and y < 500 - height - vel:
y += vel
win.fill((0,0,0))
pygame.draw.rect(win, (255,0,0), (x, y, width, height))
pygame.display.update()
pygame.quit
Now our character can not move off the screen!
Jumping
To jump we need to set up a few variables.
# These go near the top of your program, outside the while loop
isJump = False
jumpCount = 10
Now we are going to check to see if the user hits the space bar.
If they do we will change isJump to True and start jumping.
# Goes inside the while loop, under event for moving down
if keys[]pygame.K_SPACE]:
isJump = True
Now when we are jumping we do not want to allow the user to jump again or to be able to move up and down.
To disallow the user from doing this we will add another if/else statement.
import pygame
pygame.init()
win = pygame.display.set_mode((500,500))
pygame.display.set_caption("First Game")
x = 50
y = 50
width = 40
height = 60
vel = 5
isJump = False
jumpCount = 10
run = True
while run:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[]pygame.K_LEFT] and x > vel: # Making sure the top left position of our character is greater than our vel so we never move off the screen.
x -= vel
if keys[]pygame.K_RIGHT] and x < 500 - vel - width: # Making sure the top right corner of our character is less than the screen width - its width
x += vel
if not(isJump): # Checks is user is not jumping
if keys[]pygame.K_UP] and y > vel: # Same principles apply for the y coordinate
y -= vel
if keys[]pygame.K_DOWN] and y < 500 - height - vel:
y += vel
if keys[]pygame.K_SPACE]:
isJump = True
else:
# This is what will happen if we are jumping
win.fill((0,0,0))
pygame.draw.rect(win, (255,0,0), (x, y, width, height))
pygame.display.update()
pygame.quit
We are now going to start writing the code for making the character jump.
We are going to implement a quadratic formula for our jumping.
This is because ideally we would like our jump to be smooth and look something like this:
Inside of the else we will paste the following.
if jumpCount >= -10:
y -= (jumpCount * abs(jumpCount)) * 0.5
jumpCount -= 1
else: # This will execute if our jump is finished
jumpCount = 10
isJump = False
# Resetting our Variables
And now our character can jump!
*If you are confused by this please refer to the video starting at 11:00.
*Note: I use abs() use instead of ** just to eliminate the need for the neg variable seen in the video.
Both versions of this code work the same.
Full Code
Here is what your full code should look like.
import pygame
pygame.init()
win = pygame.display.set_mode((500,500))
pygame.display.set_caption("First Game")
x = 50
y = 50
width = 40
height = 60
vel = 5
isJump = False
jumpCount = 10
run = True
while run:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[]pygame.K_LEFT] and x > vel:
x -= vel
if keys[]pygame.K_RIGHT] and x < 500 - vel - width:
x += vel
if not(isJump):
if keys[]pygame.K_UP] and y > vel:
y -= vel
if keys[]pygame.K_DOWN] and y < 500 - height - vel:
y += vel
if keys[]pygame.K_SPACE]:
isJump = True
else:
if jumpCount >= -10:
y -= (jumpCount * abs(jumpCount)) * 0.5
jumpCount -= 1
else:
jumpCount = 10
isJump = False
win.fill((0,0,0))
pygame.draw.rect(win, (255,0,0), (x, y, width, height))
pygame.display.update()
pygame.quit
Character Animation & Sprites
youtube
*IMPORTANT* Please download the images for this tutorial from my GitHub or the download button below.
GitHub: Click Here!
Download Zip: Download NowPlease place all of these images in the same directory as your python script.
Character Animation
After we've downloaded our images we can start getting ready to anime our character.
The first step is to create a few variables which will be used to determine which way we are facing and keep track of the current image we are showing.
# This goes outside the while loop, near the top of our program
left = False
right = False
walkCount = 0
Now we need to load our images.
To load an image in pygame we use pygame.image.load(path).
Since we need to load so many images we will be storing some of them in lists for easier accessing later on.
This is the code to load all of our images (I recommend you copy and paste).
# This goes outside the while loop, near the top of the program
walkRight = []pygame.image.load('R1.png'), pygame.image.load('R2.png'), pygame.image.load('R3.png'), pygame.image.load('R4.png'), pygame.image.load('R5.png'), pygame.image.load('R6.png'), pygame.image.load('R7.png'), pygame.image.load('R8.png'), pygame.image.load('R9.png')]
walkLeft = []pygame.image.load('L1.png'), pygame.image.load('L2.png'), pygame.image.load('L3.png'), pygame.image.load('L4.png'), pygame.image.load('L5.png'), pygame.image.load('L6.png'), pygame.image.load('L7.png'), pygame.image.load('L8.png'), pygame.image.load('L9.png')]
bg = pygame.image.load('bg.jpg')
char = pygame.image.load('standing.png')
It is often good practice to do all of our drawing from within a function.
This means we are going to move our old drawing code that is inside the main loop into a function and make a few modifications.
import pygame
pygame.init()
win = pygame.display.set_mode((500,500))
pygame.display.set_caption("First Game")
walkRight = []pygame.image.load('R1.png'), pygame.image.load('R2.png'), pygame.image.load('R3.png'), pygame.image.load('R4.png'), pygame.image.load('R5.png'), pygame.image.load('R6.png'), pygame.image.load('R7.png'), pygame.image.load('R8.png'), pygame.image.load('R9.png')]
walkLeft = []pygame.image.load('L1.png'), pygame.image.load('L2.png'), pygame.image.load('L3.png'), pygame.image.load('L4.png'), pygame.image.load('L5.png'), pygame.image.load('L6.png'), pygame.image.load('L7.png'), pygame.image.load('L8.png'), pygame.image.load('L9.png')]
bg = pygame.image.load('bg.jpg')
char = pygame.image.load('standing.png')
x = 50
y = 50
width = 40
height = 60
vel = 5
isJump = False
jumpCount = 10
left = False
right = False
walkCount = 0
def redrawGameWindow():
global walkCount
win.blit(bg, (0,0)) # This will draw our background image at (0,0)
pygame.display.update()
run = True
while run:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and x > vel:
x -= vel
if keys[pygame.K_RIGHT] and x < 500 - vel - width:
x += vel
if not(isJump):
if keys[pygame.K_SPACE]:
isJump = True
else:
if jumpCount >= -10:
y -= (jumpCount * abs(jumpCount)) * 0.5
jumpCount -= 1
else:
jumpCount = 10
isJump = False
redrawGameWindow()
pygame.quit
Now we are going to set our left and right variables appropriately from inside the main loop.
import pygame
pygame.init()
win = pygame.display.set_mode((500,500))
pygame.display.set_caption("First Game")
walkRight = []pygame.image.load('R1.png'), pygame.image.load('R2.png'), pygame.image.load('R3.png'), pygame.image.load('R4.png'), pygame.image.load('R5.png'), pygame.image.load('R6.png'), pygame.image.load('R7.png'), pygame.image.load('R8.png'), pygame.image.load('R9.png')]
walkLeft = []pygame.image.load('L1.png'), pygame.image.load('L2.png'), pygame.image.load('L3.png'), pygame.image.load('L4.png'), pygame.image.load('L5.png'), pygame.image.load('L6.png'), pygame.image.load('L7.png'), pygame.image.load('L8.png'), pygame.image.load('L9.png')]
bg = pygame.image.load('bg.jpg')
char = pygame.image.load('standing.png')
x = 50
y = 50
width = 40
height = 60
vel = 5
isJump = False
jumpCount = 10
left = False
right = False
walkCount = 0
def redrawGameWindow():
global walkCount
win.blit(bg, (0,0)) # This will draw our background image at (0,0)
pygame.display.update()
run = True
while run:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and x > vel:
x -= vel
left = True
right = False
elif keys[pygame.K_RIGHT] and x < 500 - vel - width:
x += vel
left = False
right = True
else: # If the character is not moving we will set both left and right false and reset the animation counter (walkCount)
left = False
right = False
walkCount = 0
if not(isJump):
if keys[pygame.K_SPACE]:
isJump = True
right = False
left = False
walkCount = 0
else:
if jumpCount >= -10:
y -= (jumpCount * abs(jumpCount)) * 0.5
jumpCount -= 1
else:
jumpCount = 10
isJump = False
redrawGameWindow()
pygame.quit
And finally we can start working on drawing our character to the screen.
NOTE: This is very complex, please refer to the video for more in depth explanations.
def redrawGameWindow():
# We have 9 images for our walking animation, I want to show the same image for 3 frames
# so I use the number 27 as an upper bound for walkCount because 27 / 3 = 9.
9 images shown
# 3 times each animation.
global walkCount
win.blit(bg, (0,0))
if walkCount + 1 >= 27:
walkCount = 0
if left: # If we are facing left
win.blit(walkLeft[walkCount//3], (x,y)) # We integer divide walkCounr by 3 to ensure each
walkCount += 1 # image is shown 3 times every animation
elif right:
win.blit(walkRight[walkCount//3], (x,y))
walkCount += 1
else:
win.blit(char, (x, y)) # If the character is standing still
pygame.display.update()
The last thing we need to do is change our framerate.
To do this we are going to create a clock object using
clock = pygame.time.Clock() outside of our main loop
and write
clock.tick(27) inside our main loop.
You may notice when running the program that the background does not cover the entire screen, to fix this simply change there screen height to 480. pygame.display.set_mode((500, 480))
Full Code
The full code should look like the following.
import pygame
pygame.init()
win = pygame.display.set_mode((500,480))
pygame.display.set_caption("First Game")
walkRight = []pygame.image.load('R1.png'), pygame.image.load('R2.png'), pygame.image.load('R3.png'), pygame.image.load('R4.png'), pygame.image.load('R5.png'), pygame.image.load('R6.png'), pygame.image.load('R7.png'), pygame.image.load('R8.png'), pygame.image.load('R9.png')]
walkLeft = []pygame.image.load('L1.png'), pygame.image.load('L2.png'), pygame.image.load('L3.png'), pygame.image.load('L4.png'), pygame.image.load('L5.png'), pygame.image.load('L6.png'), pygame.image.load('L7.png'), pygame.image.load('L8.png'), pygame.image.load('L9.png')]
bg = pygame.image.load('bg.jpg')
char = pygame.image.load('standing.png')
x = 50
y = 400
width = 40
height = 60
vel = 5
clock = pygame.time.Clock()
isJump = False
jumpCount = 10
left = False
right = False
walkCount = 0
def redrawGameWindow():
global walkCount
win.blit(bg, (0,0))
if walkCount + 1 >= 27:
walkCount = 0
if left:
win.blit(walkLeft[]walkCount//3], (x,y))
walkCount += 1
elif right:
win.blit(walkRight[]walkCount//3], (x,y))
walkCount += 1
else:
win.blit(char, (x, y))
walkCount = 0
pygame.display.update()
run = True
while run:
clock.tick(27)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[]pygame.K_LEFT] and x > vel:
x -= vel
left = True
right = False
elif keys[]pygame.K_RIGHT] and x < 500 - vel - width:
x += vel
left = False
right = True
else:
left = False
right = False
walkCount = 0
if not(isJump):
if keys[]pygame.K_SPACE]:
isJump = True
left = False
right = False
walkCount = 0
else:
if jumpCount >= -10:
y -= (jumpCount * abs(jumpCount)) * 0.5
jumpCount -= 1
else:
jumpCount = 10
isJump = False
redrawGameWindow()
pygame.quit
Optimization & OOP
youtube
In this tutorial we are going to work on optimizing our code to make it more readable and scale-able.
To do this we are going to use Object Orientated Programming.Since this video is mainly translating our code into a different form I am not going to explain much of the code.
If you'd like detailed explanations please refer to the video.
Optimized Code
This is what our optimized code looks like.
import pygame
pygame.init()
win = pygame.display.set_mode((500,480))
pygame.display.set_caption("First Game")
walkRight = []pygame.image.load('R1.png'), pygame.image.load('R2.png'), pygame.image.load('R3.png'), pygame.image.load('R4.png'), pygame.image.load('R5.png'), pygame.image.load('R6.png'), pygame.image.load('R7.png'), pygame.image.load('R8.png'), pygame.image.load('R9.png')]
walkLeft = []pygame.image.load('L1.png'), pygame.image.load('L2.png'), pygame.image.load('L3.png'), pygame.image.load('L4.png'), pygame.image.load('L5.png'), pygame.image.load('L6.png'), pygame.image.load('L7.png'), pygame.image.load('L8.png'), pygame.image.load('L9.png')]
bg = pygame.image.load('bg.jpg')
char = pygame.image.load('standing.png')
clock = pygame.time.Clock()
class player(object):
def __init__(self,x,y,width,height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 5
self.isJump = False
self.left = False
self.right = False
self.walkCount = 0
self.jumpCount = 10
def draw(self, win):
if self.walkCount + 1 >= 27:
self.walkCount = 0
if self.left:
win.blit(walkLeft[]self.walkCount//3], (self.x,self.y))
self.walkCount += 1
elif self.right:
win.blit(walkRight[]self.walkCount//3], (self.x,self.y))
self.walkCount +=1
else:
win.blit(char, (self.x,self.y))
def redrawGameWindow():
win.blit(bg, (0,0))
man.draw(win)
pygame.display.update()
#mainloop
man = player(200, 410, 64,64)
run = True
while run:
clock.tick(27)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[]pygame.K_LEFT] and man.x > man.vel:
man.x -= man.vel
man.left = True
man.right = False
elif keys[]pygame.K_RIGHT] and man.x < 500 - man.width - man.vel:
man.x += man.vel
man.right = True
man.left = False
else:
man.right = False
man.left = False
man.walkCount = 0
if not(man.isJump):
if keys[]pygame.K_SPACE]:
man.isJump = True
man.right = False
man.left = False
man.walkCount = 0
else:
if man.jumpCount >= -10:
neg = 1
if man.jumpCount < 0:
neg = -1
man.y -= (man.jumpCount ** 2) * 0.5 * neg
man.jumpCount -= 1
else:
man.isJump = False
man.jumpCount = 10
redrawGameWindow()
pygame.quit
Essentially all we have done here is implement a player class that contains all of the variables we used in the previous tutorials as attributes.
It also performs the animation and drawing of the character from within the class.
Projectiles
youtube
In this tutorial we are going to be implementing projectiles.
Projectile Class
The first thing we are going to do is create a projectile class.
class projectile(object):
def __init__(self,x,y,radius,color,facing):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.facing = facing
self.vel = 8 * facing
def draw(self,win):
pygame.draw.circle(win, self.color, (self.x,self.y), self.radius)
Modifying the Player Class
Because we are going to be shooting bullets left or right we need to ensure our character is always facing left or right.
This required modifying the player.draw method and adding another attribute of standing to our player.
class player(object):
def __init__(self,x,y,width,height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 5
self.isJump = False
self.left = False
self.right = False
self.walkCount = 0
self.jumpCount = 10
self.standing = True
def draw(self, win):
if self.walkCount + 1 >= 27:
self.walkCount = 0
if not(self.standing):
if self.left:
win.blit(walkLeft[]self.walkCount//3], (self.x,self.y))
self.walkCount += 1
elif self.right:
win.blit(walkRight[]self.walkCount//3], (self.x,self.y))
self.walkCount +=1
else:
if self.right:
win.blit(walkRight[]0], (self.x, self.y))
else:
win.blit(walkLeft[]0], (self.x, self.y))
Modifying the Main Loop
We also need to modify our main loop to ensure our character is always facing left or right.
man = player(200, 410, 64,64)
run = True
while run:
clock.tick(27)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[]pygame.K_LEFT] and man.x > man.vel:
man.x -= man.vel
man.left = True
man.right = False
man.standing = False # NEW
elif keys[]pygame.K_RIGHT] and man.x < 500 - man.width - man.vel:
man.x += man.vel
man.right = True
man.left = False
man.standing = False # NEW
else:
main.standing = True # NEW (removed two lines)
man.walkCount = 0
if not(man.isJump):
if keys[]pygame.K_UP]:
man.isJump = True
man.right = False
man.left = False
man.walkCount = 0
else:
if man.jumpCount >= -10:
neg = 1
if man.jumpCount < 0:
neg = -1
man.y -= (man.jumpCount ** 2) * 0.5 * neg
man.jumpCount -= 1
else:
man.isJump = False
man.jumpCount = 10
redrawGameWindow()
Setting up The Projectiles
The first step is to create a list which will store all of our bullet objects.
bullets = [] # This goes right above the while loop
Now we are going to add a for loop into our main loop that will move our bullets and remove them if they have left the screen.
# Goes inside the while loop
for bullet in bullets:
if bullet.x < 500 and bullet.x > 0:
bullet.x += bullet.vel # Moves the bullet by its vel
else:
bullets.pop(bullets.index(bullet)) # This will remove the bullet if it is off the screen
Shooting the Bullet
We are first going to change our jump key to be the up arrow (just change pygame.K_SPACE to pygame.K_UP).
Then we are going to create a new if statement that will check if the space bar is clicked.
If it is we will create a new bullet, give it a velocity and start moving it.
# Goes inside the while loop, under keys = ...
if keys[]pygame.K_SPACE]:
if man.left:
facing = -1
else:
facing = 1
if len(bullets) < 5: # This will make sure we cannot exceed 5 bullets on the screen at once
bullets.append(projectile(round(man.x+man.width//2), round(man.y + man.height//2), 6, (0,0,0), facing))
# This will create a bullet starting at the middle of the character
And the last thing to do is to draw our bullets inside redrawGameWindow().
def redrawGameWindow():
win.blit(bg, (0,0))
man.draw(win)
for bullet in bullets:
bullet.draw(win)
pygame.display.update()
Now we can fire bullets!
Full Code
The full code should look like this.
import pygame
pygame.init()
win = pygame.display.set_mode((500,480))
pygame.display.set_caption("First Game")
walkRight = []pygame.image.load('R1.png'), pygame.image.load('R2.png'), pygame.image.load('R3.png'), pygame.image.load('R4.png'), pygame.image.load('R5.png'), pygame.image.load('R6.png'), pygame.image.load('R7.png'), pygame.image.load('R8.png'), pygame.image.load('R9.png')]
walkLeft = []pygame.image.load('L1.png'), pygame.image.load('L2.png'), pygame.image.load('L3.png'), pygame.image.load('L4.png'), pygame.image.load('L5.png'), pygame.image.load('L6.png'), pygame.image.load('L7.png'), pygame.image.load('L8.png'), pygame.image.load('L9.png')]
bg = pygame.image.load('bg.jpg')
char = pygame.image.load('standing.png')
clock = pygame.time.Clock()
class player(object):
def __init__(self,x,y,width,height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 5
self.isJump = False
self.left = False
self.right = False
self.walkCount = 0
self.jumpCount = 10
self.standing = True
def draw(self, win):
if self.walkCount + 1 >= 27:
self.walkCount = 0
if not(self.standing):
if self.left:
win.blit(walkLeft[]self.walkCount//3], (self.x,self.y))
self.walkCount += 1
elif self.right:
win.blit(walkRight[]self.walkCount//3], (self.x,self.y))
self.walkCount +=1
else:
if self.right:
win.blit(walkRight[]0], (self.x, self.y))
else:
win.blit(walkLeft[]0], (self.x, self.y))
class projectile(object):
def __init__(self,x,y,radius,color,facing):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.facing = facing
self.vel = 8 * facing
def draw(self,win):
pygame.draw.circle(win, self.color, (self.x,self.y), self.radius)
def redrawGameWindow():
win.blit(bg, (0,0))
man.draw(win)
for bullet in bullets:
bullet.draw(win)
pygame.display.update()
#mainloop
man = player(200, 410, 64,64)
bullets = []
run = True
while run:
clock.tick(27)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for bullet in bullets:
if bullet.x < 500 and bullet.x > 0:
bullet.x += bullet.vel
else:
bullets.pop(bullets.index(bullet))
keys = pygame.key.get_pressed()
if keys[]pygame.K_SPACE]:
if man.left:
facing = -1
else:
facing = 1
if len(bullets) < 5:
bullets.append(projectile(round(man.x + man.width //2), round(man.y + man.height//2), 6, (0,0,0), facing))
if keys[]pygame.K_LEFT] and man.x > man.vel:
man.x -= man.vel
man.left = True
man.right = False
man.standing = False
elif keys[]pygame.K_RIGHT] and man.x < 500 - man.width - man.vel:
man.x += man.vel
man.right = True
man.left = False
man.standing = False
else:
man.standing = True
man.walkCount = 0
if not(man.isJump):
if keys[]pygame.K_UP]:
man.isJump = True
man.right = False
man.left = False
man.walkCount = 0
else:
if man.jumpCount >= -10:
neg = 1
if man.jumpCount < 0:
neg = -1
man.y -= (man.jumpCount ** 2) * 0.5 * neg
man.jumpCount -= 1
else:
man.isJump = False
man.jumpCount = 10
redrawGameWindow()
pygame.quit
Enemies
youtube
In this tutorial we will be creating enemies that can move around the screen following a predefined path.*IMPORTANT* You will need to download the images below before starting.
Images
If you have been following since the first tutorial you should already have these images, if not please download them from one of the options below.GitHub: Click Here!Download Zip: Download NowPlease ensure you place these images in the same directory as your python script.Note: The folder you downloaded has the images and assets for the entire finished game.
The images that we will be working with in this tutorial end in an E.png
Loading Images
The following lists will be used a class variables to store our images for the enemy.
We will place these lists inside of a class that we create in the next step.
walkRight = []pygame.image.load('R1E.png'), pygame.image.load('R2E.png'), pygame.image.load('R3E.png'), pygame.image.load('R4E.png'), pygame.image.load('R5E.png'), pygame.image.load('R6E.png'), pygame.image.load('R7E.png'), pygame.image.load('R8E.png'), pygame.image.load('R9E.png'), pygame.image.load('R10E.png'), pygame.image.load('R11E.png')]
walkLeft = []pygame.image.load('L1E.png'), pygame.image.load('L2E.png'), pygame.image.load('L3E.png'), pygame.image.load('L4E.png'), pygame.image.load('L5E.png'), pygame.image.load('L6E.png'), pygame.image.load('L7E.png'), pygame.image.load('L8E.png'), pygame.image.load('L9E.png'), pygame.image.load('L10E.png'), pygame.image.load('L11E.png')]
Enemy Class
Like we've done in the previous tutorials we will use a class to create our enemy.
We will start by pasting the code from above into our class.
Then we will create the __init__ method and set up some basic attributes for our enemy.
class enemy(object):
walkRight = []pygame.image.load('R1E.png'), pygame.image.load('R2E.png'), pygame.image.load('R3E.png'), pygame.image.load('R4E.png'), pygame.image.load('R5E.png'), pygame.image.load('R6E.png'), pygame.image.load('R7E.png'), pygame.image.load('R8E.png'), pygame.image.load('R9E.png'), pygame.image.load('R10E.png'), pygame.image.load('R11E.png')]
walkLeft = []pygame.image.load('L1E.png'), pygame.image.load('L2E.png'), pygame.image.load('L3E.png'), pygame.image.load('L4E.png'), pygame.image.load('L5E.png'), pygame.image.load('L6E.png'), pygame.image.load('L7E.png'), pygame.image.load('L8E.png'), pygame.image.load('L9E.png'), pygame.image.load('L10E.png'), pygame.image.load('L11E.png')]
def __init__(self, x, y, width, height, end):
self.x = x
self.y = y
self.width = width
self.height = height
self.path = []x, end] # This will define where our enemy starts and finishes their path.
self.walkCount = 0
self.vel = 3
The next method we will add will be the draw() method.
This will work similarly to our player classes draw method as we will also be doing animation in here.
# Goes inside the enemy class
def draw(self, win):
self.move()
if self.walkCount + 1 >= 33: # Since we have 11 images for each animtion our upper bound is 33.
# We will show each image for 3 frames. 3 x 11 = 33.
self.walkCount = 0
if self.vel > 0: # If we are moving to the right we will display our walkRight images
win.blit(self.walkRight[]self.walkCount//3], (self.x,self.y))
self.walkCount += 1
else: # Otherwise we will display the walkLeft images
win.blit(self.walkLeft[]self.walkCount//3], (self.x,self.y))
self.walkCount += 1
The next method we add will be the move() method.
This will move our enemy every frame.
Since we want our enemy to be moving along a path we must figure out how much to move the enemy by and in what direction.
We call this move method from our draw class as every time we draw the enemy we want to move first move it to a new position.
# Goes inside the enemy class
def move(self):
if self.vel > 0: # If we are moving right
if self.x < self.path[]1] + self.vel: # If we have not reached the furthest right point on our path.
self.x += self.vel
else: # Change direction and move back the other way
self.vel = self.vel * -1
self.x += self.vel
self.walkCount = 0
else: # If we are moving left
if self.x > self.path[]0] - self.vel: # If we have not reached the furthest left point on our path
self.x += self.vel
else: # Change direction
self.vel = self.vel * -1
self.x += self.vel
self.walkCount = 0
Creating an Enemy
Now that we've created an enemy class we need to create an instance of that class.
We will do this the same way we created an instance of our player class (man).
Just outside and above our main loop.
goblin = enemy(100, 410, 64, 64, 300)
Now that we've created an instance we need to draw our goblin on the window.
We will place the following code inside or redrawGameWindow function.
goblin.draw()
And now our goblin should be appearing on the screen and walking around!
Full Code
This is what the full code looks like now.
import pygame
pygame.init()
win = pygame.display.set_mode((500,480))
pygame.display.set_caption("First Game")
walkRight = []pygame.image.load('R1.png'), pygame.image.load('R2.png'), pygame.image.load('R3.png'), pygame.image.load('R4.png'), pygame.image.load('R5.png'), pygame.image.load('R6.png'), pygame.image.load('R7.png'), pygame.image.load('R8.png'), pygame.image.load('R9.png')]
walkLeft = []pygame.image.load('L1.png'), pygame.image.load('L2.png'), pygame.image.load('L3.png'), pygame.image.load('L4.png'), pygame.image.load('L5.png'), pygame.image.load('L6.png'), pygame.image.load('L7.png'), pygame.image.load('L8.png'), pygame.image.load('L9.png')]
bg = pygame.image.load('bg.jpg')
char = pygame.image.load('standing.png')
clock = pygame.time.Clock()
class player(object):
def __init__(self,x,y,width,height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 5
self.isJump = False
self.left = False
self.right = False
self.walkCount = 0
self.jumpCount = 10
self.standing = True
def draw(self, win):
if self.walkCount + 1 >= 27:
self.walkCount = 0
if not(self.standing):
if self.left:
win.blit(walkLeft[]self.walkCount//3], (self.x,self.y))
self.walkCount += 1
elif self.right:
win.blit(walkRight[]self.walkCount//3], (self.x,self.y))
self.walkCount +=1
else:
if self.right:
win.blit(walkRight[]0], (self.x, self.y))
else:
win.blit(walkLeft[]0], (self.x, self.y))
class projectile(object):
def __init__(self,x,y,radius,color,facing):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.facing = facing
self.vel = 8 * facing
def draw(self,win):
pygame.draw.circle(win, self.color, (self.x,self.y), self.radius)
class enemy(object):
walkRight = []pygame.image.load('R1E.png'), pygame.image.load('R2E.png'), pygame.image.load('R3E.png'), pygame.image.load('R4E.png'), pygame.image.load('R5E.png'), pygame.image.load('R6E.png'), pygame.image.load('R7E.png'), pygame.image.load('R8E.png'), pygame.image.load('R9E.png'), pygame.image.load('R10E.png'), pygame.image.load('R11E.png')]
walkLeft = []pygame.image.load('L1E.png'), pygame.image.load('L2E.png'), pygame.image.load('L3E.png'), pygame.image.load('L4E.png'), pygame.image.load('L5E.png'), pygame.image.load('L6E.png'), pygame.image.load('L7E.png'), pygame.image.load('L8E.png'), pygame.image.load('L9E.png'), pygame.image.load('L10E.png'), pygame.image.load('L11E.png')]
def __init__(self, x, y, width, height, end):
self.x = x
self.y = y
self.width = width
self.height = height
self.path = []x, end]
self.walkCount = 0
self.vel = 3
def draw(self, win):
self.move()
if self.walkCount + 1 >= 33:
self.walkCount = 0
if self.vel > 0:
win.blit(self.walkRight[]self.walkCount//3], (self.x,self.y))
self.walkCount += 1
else:
win.blit(self.walkLeft[]self.walkCount//3], (self.x,self.y))
self.walkCount += 1
def move(self):
if self.vel > 0:
if self.x < self.path[]1] + self.vel:
self.x += self.vel
else:
self.vel = self.vel * -1
self.x += self.vel
self.walkCount = 0
else:
if self.x > self.path[]0] - self.vel:
self.x += self.vel
else:
self.vel = self.vel * -1
self.x += self.vel
self.walkCount = 0
def redrawGameWindow():
win.blit(bg, (0,0))
man.draw(win)
goblin.draw(win)
for bullet in bullets:
bullet.draw(win)
pygame.display.update()
#mainloop
man = player(200, 410, 64,64)
goblin = enemy(100, 410, 64, 64, 300)
bullets = []
run = True
while run:
clock.tick(27)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for bullet in bullets:
if bullet.x < 500 and bullet.x > 0:
bullet.x += bullet.vel
else:
bullets.pop(bullets.index(bullet))
keys = pygame.key.get_pressed()
if keys[]pygame.K_SPACE]:
if man.left:
facing = -1
else:
facing = 1
if len(bullets) < 5:
bullets.append(projectile(round(man.x + man.width //2), round(man.y + man.height//2), 6, (0,0,0), facing))
if keys[]pygame.K_LEFT] and man.x > man.vel:
man.x -= man.vel
man.left = True
man.right = False
man.standing = False
elif keys[]pygame.K_RIGHT] and man.x < 500 - man.width - man.vel:
man.x += man.vel
man.right = True
man.left = False
man.standing = False
else:
man.standing = True
man.walkCount = 0
if not(man.isJump):
if keys[]pygame.K_UP]:
man.isJump = True
man.right = False
man.left = False
man.walkCount = 0
else:
if man.jumpCount >= -10:
neg = 1
if man.jumpCount < 0:
neg = -1
man.y -= (man.jumpCount ** 2) * 0.5 * neg
man.jumpCount -= 1
else:
man.isJump = False
man.jumpCount = 10
redrawGameWindow()
pygame.quit
Collision & Hit Boxes
youtube
In many games we need to check for different types of collision.
Whether that be collision of our mouse with a button or collision between images and/or objects.
In our case we want to check if our goblin has hit the player or if our bullets have hit the goblin.
Hit Boxes
The term "hit box" is often used to represent the box around an object which represents its "hittable space".
Since we often use complex objects and shapes to depict characters or other items in a game we create a hit box for all of these items.
This makes it much easier to check for collision as collision between non-rectangular shapes is extremely complicated.
We attempt to make these boxes fit the characters shape as precisely as possible but it is difficult to make them perfect.We will start by creating hit boxes for all of our objects.
Then we check if these boxes collide with one another using some basic math.The first hit box we define will be for our player class.
# This goes inside the player class in the __init__ method
self.hitbox = (self.x + 20, self.y, 28, 60)
# The elements in the hitbox are (top left x, top left y, width, height)
Since our player moves we will have to constantly redefine the hit box from within the draw method of our player class.
To do this we will simply copy the line from above into the draw method.
Our player class should now look like the following.
class player(object):
def __init__(self,x,y,width,height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 5
self.isJump = False
self.left = False
self.right = False
self.walkCount = 0
self.jumpCount = 10
self.standing = True
self.hitbox = (self.x + 17, self.y + 11, 29, 52) # NEW
def draw(self, win):
if self.walkCount + 1 >= 27:
self.walkCount = 0
if not(self.standing):
if self.left:
win.blit(walkLeft[]self.walkCount//3], (self.x,self.y))
self.walkCount += 1
elif self.right:
win.blit(walkRight[]self.walkCount//3], (self.x,self.y))
self.walkCount +=1
else:
if self.right:
win.blit(walkRight[]0], (self.x, self.y))
else:
win.blit(walkLeft[]0], (self.x, self.y))
self.hitbox = (self.x + 17, self.y + 11, 29, 52) # NEW
pygame.draw.rect(win, (255,0,0), self.hitbox,2) # To draw the hit box around the player
We will repeat this process in the enemy class. And after adding in the hit box we will also define a new method called hit.
class enemy(object):
walkRight = []pygame.image.load('R1E.png'), pygame.image.load('R2E.png'), pygame.image.load('R3E.png'), pygame.image.load('R4E.png'), pygame.image.load('R5E.png'), pygame.image.load('R6E.png'), pygame.image.load('R7E.png'), pygame.image.load('R8E.png'), pygame.image.load('R9E.png'), pygame.image.load('R10E.png'), pygame.image.load('R11E.png')]
walkLeft = []pygame.image.load('L1E.png'), pygame.image.load('L2E.png'), pygame.image.load('L3E.png'), pygame.image.load('L4E.png'), pygame.image.load('L5E.png'), pygame.image.load('L6E.png'), pygame.image.load('L7E.png'), pygame.image.load('L8E.png'), pygame.image.load('L9E.png'), pygame.image.load('L10E.png'), pygame.image.load('L11E.png')]
def __init__(self, x, y, width, height, end):
self.x = x
self.y = y
self.width = width
self.height = height
self.end = end
self.path = []self.x, self.end]
self.walkCount = 0
self.vel = 3
self.hitbox = (self.x + 17, self.y + 2, 31, 57) # NEW
def draw(self,win):
self.move()
if self.walkCount + 1 >= 33:
self.walkCount = 0
if self.vel > 0:
win.blit(self.walkRight[]self.walkCount //3], (self.x, self.y))
self.walkCount += 1
else:
win.blit(self.walkLeft[]self.walkCount //3], (self.x, self.y))
self.walkCount += 1
self.hitbox = (self.x + 17, self.y + 2, 31, 57) # NEW
pygame.draw.rect(win, (255,0,0), self.hitbox,2) # Draws the hit box around the enemy
def move(self):
if self.vel > 0:
if self.x + self.vel < self.path[]1]:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount = 0
else:
if self.x - self.vel > self.path[]0]:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount = 0
# NEW METHOD
def hit(self): # This will display when the enemy is hit
print('hit')
If we run the program we can see the hit boxes for our character.
Collision
The fist collision we will check for is between the bullets and the enemy.
Every time we move a bullet we will check if it has collided with the enemy.
Since we already have a for loop setup to check if the bullets leave the screen we will do our collision check in there.We are going to say these objects have collided if the x and y coordinate of the bullet sit inside the hit box of the enemy.
We check this with the following code.
if bullet.y - bullet.radius < goblin.hitbox[]1] + goblin.hitbox[]3] and bullet.y + bullet.radius > goblin.hitbox[]1]: # Checks x coords
if bullet.x + bullet.radius > goblin.hitbox[]0] and bullet.x - bullet.radius < goblin.hitbox[]0] + goblin.hitbox[]2]: # Checks y coords
goblin.hit() # calls enemy hit method
bullets.pop(bullets.index(bullet)) # removes bullet from bullet list
Bullet Glitch
There is a small glitch you may have noticed which causes our bullets to stick together or shoot multiple at the same time.
To fix this we must do the following.First we are going to create a variable called shootLoop outside of our main loop.
shootLoop = 0
After that we will place the following code at the top of our while loop.
if shootLoop > 0:
shootLoop += 1
if shootLoop > 3:
shootLoop = 0
Then we will modify our space bar event check the following way.
if keys[]pygame.K_SPACE] and shootLoop == 0:
...
# Add the "and shootLoop == 0"
Full Code
After all this the final code should look like:
import pygame
pygame.init()
win = pygame.display.set_mode((500,480))
pygame.display.set_caption("First Game")
walkRight = []pygame.image.load('R1.png'), pygame.image.load('R2.png'), pygame.image.load('R3.png'), pygame.image.load('R4.png'), pygame.image.load('R5.png'), pygame.image.load('R6.png'), pygame.image.load('R7.png'), pygame.image.load('R8.png'), pygame.image.load('R9.png')]
walkLeft = []pygame.image.load('L1.png'), pygame.image.load('L2.png'), pygame.image.load('L3.png'), pygame.image.load('L4.png'), pygame.image.load('L5.png'), pygame.image.load('L6.png'), pygame.image.load('L7.png'), pygame.image.load('L8.png'), pygame.image.load('L9.png')]
bg = pygame.image.load('bg.jpg')
char = pygame.image.load('standing.png')
clock = pygame.time.Clock()
class player(object):
def __init__(self,x,y,width,height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 5
self.isJump = False
self.left = False
self.right = False
self.walkCount = 0
self.jumpCount = 10
self.standing = True
self.hitbox = (self.x + 17, self.y + 11, 29, 52)
def draw(self, win):
if self.walkCount + 1 >= 27:
self.walkCount = 0
if not(self.standing):
if self.left:
win.blit(walkLeft[]self.walkCount//3], (self.x,self.y))
self.walkCount += 1
elif self.right:
win.blit(walkRight[]self.walkCount//3], (self.x,self.y))
self.walkCount +=1
else:
if self.right:
win.blit(walkRight[]0], (self.x, self.y))
else:
win.blit(walkLeft[]0], (self.x, self.y))
self.hitbox = (self.x + 17, self.y + 11, 29, 52)
pygame.draw.rect(win, (255,0,0), self.hitbox,2)
class projectile(object):
def __init__(self,x,y,radius,color,facing):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.facing = facing
self.vel = 8 * facing
def draw(self,win):
pygame.draw.circle(win, self.color, (self.x,self.y), self.radius)
class enemy(object):
walkRight = []pygame.image.load('R1E.png'), pygame.image.load('R2E.png'), pygame.image.load('R3E.png'), pygame.image.load('R4E.png'), pygame.image.load('R5E.png'), pygame.image.load('R6E.png'), pygame.image.load('R7E.png'), pygame.image.load('R8E.png'), pygame.image.load('R9E.png'), pygame.image.load('R10E.png'), pygame.image.load('R11E.png')]
walkLeft = []pygame.image.load('L1E.png'), pygame.image.load('L2E.png'), pygame.image.load('L3E.png'), pygame.image.load('L4E.png'), pygame.image.load('L5E.png'), pygame.image.load('L6E.png'), pygame.image.load('L7E.png'), pygame.image.load('L8E.png'), pygame.image.load('L9E.png'), pygame.image.load('L10E.png'), pygame.image.load('L11E.png')]
def __init__(self, x, y, width, height, end):
self.x = x
self.y = y
self.width = width
self.height = height
self.end = end
self.path = []self.x, self.end]
self.walkCount = 0
self.vel = 3
self.hitbox = (self.x + 17, self.y + 2, 31, 57)
def draw(self,win):
self.move()
if self.walkCount + 1 >= 33:
self.walkCount = 0
if self.vel > 0:
win.blit(self.walkRight[]self.walkCount //3], (self.x, self.y))
self.walkCount += 1
else:
win.blit(self.walkLeft[]self.walkCount //3], (self.x, self.y))
self.walkCount += 1
self.hitbox = (self.x + 17, self.y + 2, 31, 57)
pygame.draw.rect(win, (255,0,0), self.hitbox,2)
def move(self):
if self.vel > 0:
if self.x + self.vel < self.path[]1]:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount = 0
else:
if self.x - self.vel > self.path[]0]:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount = 0
def hit(self):
print('hit')
def redrawGameWindow():
win.blit(bg, (0,0))
man.draw(win)
goblin.draw(win)
for bullet in bullets:
bullet.draw(win)
pygame.display.update()
#mainloop
man = player(200, 410, 64,64)
goblin = enemy(100, 410, 64, 64, 450)
shootLoop = 0
bullets = []
run = True
while run:
clock.tick(27)
if shootLoop > 0:
shootLoop += 1
if shootLoop > 3:
shootLoop = 0
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for bullet in bullets:
if bullet.y - bullet.radius < goblin.hitbox[]1] + goblin.hitbox[]3] and bullet.y + bullet.radius > goblin.hitbox[]1]:
if bullet.x + bullet.radius > goblin.hitbox[]0] and bullet.x - bullet.radius < goblin.hitbox[]0] + goblin.hitbox[]2]:
goblin.hit()
bullets.pop(bullets.index(bullet))
if bullet.x < 500 and bullet.x > 0:
bullet.x += bullet.vel
else:
bullets.pop(bullets.index(bullet))
keys = pygame.key.get_pressed()
if keys[]pygame.K_SPACE] and shootLoop == 0:
if man.left:
facing = -1
else:
facing = 1
if len(bullets) < 5:
bullets.append(projectile(round(man.x + man.width //2), round(man.y + man.height//2), 6, (0,0,0), facing))
shootLoop = 1
if keys[]pygame.K_LEFT] and man.x > man.vel:
man.x -= man.vel
man.left = True
man.right = False
man.standing = False
elif keys[]pygame.K_RIGHT] and man.x < 500 - man.width - man.vel:
man.x += man.vel
man.right = True
man.left = False
man.standing = False
else:
man.standing = True
man.walkCount = 0
if not(man.isJump):
if keys[]pygame.K_UP]:
man.isJump = True
man.right = False
man.left = False
man.walkCount = 0
else:
if man.jumpCount >= -10:
neg = 1
if man.jumpCount < 0:
neg = -1
man.y -= (man.jumpCount ** 2) * 0.5 * neg
man.jumpCount -= 1
else:
man.isJump = False
man.jumpCount = 10
redrawGameWindow()
pygame.quit
Scoring & Health Bars
youtube
Score
For simplicity we are simply going to be increasing the score each time a bullet hits our goblin/enemy.We are going to start by creating a variable at the top of our program called score.
score = 0
Next we are going to increment the score from our main loop where we check if the goblin is hit.
# This code exists inside the main loop
for bullet in bullets:
if bullet.y - bullet.radius < goblin.hitbox[]1] + goblin.hitbox[]3] and bullet.y + bullet.radius > goblin.hitbox[]1]:
if bullet.x + bullet.radius > goblin.hitbox[]0] and bullet.x - bullet.radius < goblin.hitbox[]0] + goblin.hitbox[]2]:
goblin.hit()
score += 1 # NEW CODE
bullets.pop(bullets.index(bullet))
We would like to display this score to the screen.
To do this we must first create a font object.
font = pygame.font.SysFont("comicsans", 30, True)
# The first argument is the font, next is size
# and then True to make our font bold
After we create our font we need to render some text and blit it to the screen.
# This should go inside the redrawGameWindow function
text = font.render("Score: " + str(score), 1, (0,0,0)) # Arguments are: text, anti-aliasing, color
win.blit(text, (390, 10))
Health Bar
What we are going to do now is give our enemy health and a health bar.
This way we can see when we should remove it from the screen.
To create the health bar we are going to draw two rectangles.
One green and one red.
We will change the width of the green rectangle (which will overlay the red one) each time the enemy is hit.To do this we must modify the enemy class like so.
class enemy(object):
walkRight = []pygame.image.load('R1E.png'), pygame.image.load('R2E.png'), pygame.image.load('R3E.png'), pygame.image.load('R4E.png'), pygame.image.load('R5E.png'), pygame.image.load('R6E.png'), pygame.image.load('R7E.png'), pygame.image.load('R8E.png'), pygame.image.load('R9E.png'), pygame.image.load('R10E.png'), pygame.image.load('R11E.png')]
walkLeft = []pygame.image.load('L1E.png'), pygame.image.load('L2E.png'), pygame.image.load('L3E.png'), pygame.image.load('L4E.png'), pygame.image.load('L5E.png'), pygame.image.load('L6E.png'), pygame.image.load('L7E.png'), pygame.image.load('L8E.png'), pygame.image.load('L9E.png'), pygame.image.load('L10E.png'), pygame.image.load('L11E.png')]
def __init__(self, x, y, width, height, end):
self.x = x
self.y = y
self.width = width
self.height = height
self.end = end
self.path = []self.x, self.end]
self.walkCount = 0
self.vel = 3
self.hitbox = (self.x + 17, self.y + 2, 31, 57)
self.health = 10 # NEW
self.visible = True # NEW
def draw(self,win):
self.move()
if self.visible: # NEW
if self.walkCount + 1 >= 33:
self.walkCount = 0
if self.vel > 0:
win.blit(self.walkRight[]self.walkCount //3], (self.x, self.y))
self.walkCount += 1
else:
win.blit(self.walkLeft[]self.walkCount //3], (self.x, self.y))
self.walkCount += 1
pygame.draw.rect(win, (255,0,0), (self.hitbox[]0], self.hitbox[]1] - 20, 50, 10)) # NEW
pygame.draw.rect(win, (0,128,0), (self.hitbox[]0], self.hitbox[]1] - 20, 50 - (5 * (10 - self.health)), 10)) # NEW
self.hitbox = (self.x + 17, self.y + 2, 31, 57)
#pygame.draw.rect(win, (255,0,0), self.hitbox,2)
def move(self):
if self.vel > 0:
if self.x + self.vel < self.path[]1]:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount = 0
else:
if self.x - self.vel > self.path[]0]:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount = 0
def hit(self): # ALL NEW
if self.health > 0:
self.health -= 1
else:
self.visible = False
print('hit')
Now we have a health bar that will move down as our goblin is hit.
Full Code
If you are confused by any of this code the video above offers more in depth explanations.
import pygame
pygame.init()
win = pygame.display.set_mode((500,480))
pygame.display.set_caption("First Game")
walkRight = []pygame.image.load('R1.png'), pygame.image.load('R2.png'), pygame.image.load('R3.png'), pygame.image.load('R4.png'), pygame.image.load('R5.png'), pygame.image.load('R6.png'), pygame.image.load('R7.png'), pygame.image.load('R8.png'), pygame.image.load('R9.png')]
walkLeft = []pygame.image.load('L1.png'), pygame.image.load('L2.png'), pygame.image.load('L3.png'), pygame.image.load('L4.png'), pygame.image.load('L5.png'), pygame.image.load('L6.png'), pygame.image.load('L7.png'), pygame.image.load('L8.png'), pygame.image.load('L9.png')]
bg = pygame.image.load('bg.jpg')
char = pygame.image.load('standing.png')
clock = pygame.time.Clock()
score = 0
class player(object):
def __init__(self,x,y,width,height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 5
self.isJump = False
self.left = False
self.right = False
self.walkCount = 0
self.jumpCount = 10
self.standing = True
self.hitbox = (self.x + 17, self.y + 11, 29, 52)
def draw(self, win):
if self.walkCount + 1 >= 27:
self.walkCount = 0
if not(self.standing):
if self.left:
win.blit(walkLeft[]self.walkCount//3], (self.x,self.y))
self.walkCount += 1
elif self.right:
win.blit(walkRight[]self.walkCount//3], (self.x,self.y))
self.walkCount +=1
else:
if self.right:
win.blit(walkRight[]0], (self.x, self.y))
else:
win.blit(walkLeft[]0], (self.x, self.y))
self.hitbox = (self.x + 17, self.y + 11, 29, 52)
#pygame.draw.rect(win, (255,0,0), self.hitbox,2)
class projectile(object):
def __init__(self,x,y,radius,color,facing):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.facing = facing
self.vel = 8 * facing
def draw(self,win):
pygame.draw.circle(win, self.color, (self.x,self.y), self.radius)
class enemy(object):
walkRight = []pygame.image.load('R1E.png'), pygame.image.load('R2E.png'), pygame.image.load('R3E.png'), pygame.image.load('R4E.png'), pygame.image.load('R5E.png'), pygame.image.load('R6E.png'), pygame.image.load('R7E.png'), pygame.image.load('R8E.png'), pygame.image.load('R9E.png'), pygame.image.load('R10E.png'), pygame.image.load('R11E.png')]
walkLeft = []pygame.image.load('L1E.png'), pygame.image.load('L2E.png'), pygame.image.load('L3E.png'), pygame.image.load('L4E.png'), pygame.image.load('L5E.png'), pygame.image.load('L6E.png'), pygame.image.load('L7E.png'), pygame.image.load('L8E.png'), pygame.image.load('L9E.png'), pygame.image.load('L10E.png'), pygame.image.load('L11E.png')]
def __init__(self, x, y, width, height, end):
self.x = x
self.y = y
self.width = width
self.height = height
self.end = end
self.path = []self.x, self.end]
self.walkCount = 0
self.vel = 3
self.hitbox = (self.x + 17, self.y + 2, 31, 57)
self.health = 10
self.visible = True
def draw(self,win):
self.move()
if self.visible:
if self.walkCount + 1 >= 33:
self.walkCount = 0
if self.vel > 0:
win.blit(self.walkRight[]self.walkCount //3], (self.x, self.y))
self.walkCount += 1
else:
win.blit(self.walkLeft[]self.walkCount //3], (self.x, self.y))
self.walkCount += 1
pygame.draw.rect(win, (255,0,0), (self.hitbox[]0], self.hitbox[]1] - 20, 50, 10))
pygame.draw.rect(win, (0,128,0), (self.hitbox[]0], self.hitbox[]1] - 20, 50 - (5 * (10 - self.health)), 10))
self.hitbox = (self.x + 17, self.y + 2, 31, 57)
#pygame.draw.rect(win, (255,0,0), self.hitbox,2)
def move(self):
if self.vel > 0:
if self.x + self.vel < self.path[]1]:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount = 0
else:
if self.x - self.vel > self.path[]0]:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount = 0
def hit(self):
if self.health > 0:
self.health -= 1
else:
self.visible = False
print('hit')
def redrawGameWindow():
win.blit(bg, (0,0))
text = font.render('Score: ' + str(score), 1, (0,0,0))
win.blit(text, (390, 10))
man.draw(win)
goblin.draw(win)
for bullet in bullets:
bullet.draw(win)
pygame.display.update()
#mainloop
font = pygame.font.SysFont('comicsans', 30, True)
man = player(200, 410, 64,64)
goblin = enemy(100, 410, 64, 64, 450)
shootLoop = 0
bullets = []
run = True
while run:
clock.tick(27)
if shootLoop > 0:
shootLoop += 1
if shootLoop > 3:
shootLoop = 0
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for bullet in bullets:
if bullet.y - bullet.radius < goblin.hitbox[]1] + goblin.hitbox[]3] and bullet.y + bullet.radius > goblin.hitbox[]1]:
if bullet.x + bullet.radius > goblin.hitbox[]0] and bullet.x - bullet.radius < goblin.hitbox[]0] + goblin.hitbox[]2]:
goblin.hit()
score += 1
bullets.pop(bullets.index(bullet))
if bullet.x < 500 and bullet.x > 0:
bullet.x += bullet.vel
else:
bullets.pop(bullets.index(bullet))
keys = pygame.key.get_pressed()
if keys[]pygame.K_SPACE] and shootLoop == 0:
if man.left:
facing = -1
else:
facing = 1
if len(bullets) < 5:
bullets.append(projectile(round(man.x + man.width //2), round(man.y + man.height//2), 6, (0,0,0), facing))
shootLoop = 1
if keys[]pygame.K_LEFT] and man.x > man.vel:
man.x -= man.vel
man.left = True
man.right = False
man.standing = False
elif keys[]pygame.K_RIGHT] and man.x < 500 - man.width - man.vel:
man.x += man.vel
man.right = True
man.left = False
man.standing = False
else:
man.standing = True
man.walkCount = 0
if not(man.isJump):
if keys[]pygame.K_UP]:
man.isJump = True
man.right = False
man.left = False
man.walkCount = 0
else:
if man.jumpCount >= -10:
neg = 1
if man.jumpCount < 0:
neg = -1
man.y -= (man.jumpCount ** 2) * 0.5 * neg
man.jumpCount -= 1
else:
man.isJump = False
man.jumpCount = 10
redrawGameWindow()
pygame.quit
Sound Effects & Music
youtube
*IMPORTANT* Please download the audio files from the below links before starting.GitHub: Click Here!Download Zip: Download NowPlease place these audio files in the directory of your python script.Note: These links will download all of the assets for the game.
The audio files are within the folder.
If you have been following along since the beginning you should already have these files.
Loading Sounds & Music
Before we can start using our sounds and music we need to load them.
# This should be placed at the top of our program
bulletSound = pygame.mixer.Sound("bullet.wav")
hitSound = pygame.mixer.Sound("hit.wav")
music = pygame.mixer.music.load("music.mp3")
Playing Music
To have our music play continuously we do the following directly after defining our variable music.
pygame.mixer.music.play(-1) # -1 will ensure the song keeps looping
Playing Sounds
To play a sound we type:
bulletSound.play()
Since we want sounds to play whenever we shoot a bullet or the goblin is hit we will add code in the appropriate places.Inside the enemy hit method we will play the hit sound.
def hit(): # inside enemy class
hitSound.play()
...
Then when we hit the space bar we will play the bullet sound.
# This is inside the main loop
if keys[]pygame.K_SPACE] and shootLoop == 0:
bulletSound.play()
...
Character-Enemy Collision
Now we check for collision between the character and any enemies.The first thing we will do is add a method "hit" to our player class.
def hit(self):
self.x = 60 # We are resetting the player position
self.y = 410
self.walkCount = 0
font1 = pygame.font.SysFont('comicsans', 100)
text = font1.render('-5', 1, (255,0,0))
win.blit(text, (250 - (text.get_width()/2),200))
pygame.display.update()
i = 0
while i < 300:
pygame.time.delay(10)
i += 1
for event in pygame.event.get():
if event.type == pygame.QUIT:
i = 301
pygame.quit
# After we are hit we are going to display a message to the screen for
# a certain period of time
Now time to actually check for collision.
Since we've already done collision between our bullets and enemy this step will be very similar.
We can actually reuse our code from before with a few minor changes.
if man.hitbox[]1] < goblin.hitbox[]1] + goblin.hitbox[]3] and man.hitbox[]1] + man.hitbox[]3] > goblin.hitbox[]1]:
if man.hitbox[]0] + man.hitbox[]2] > goblin.hitbox[]0] and man.hitbox[]0] < goblin.hitbox[]0] + goblin.hitbox[]2]:
man.hit()
score -= 5
# This will go at the top of or main loop.
Full Code
import pygame
pygame.init()
win = pygame.display.set_mode((500,480))
pygame.display.set_caption("First Game")
walkRight = []pygame.image.load('R1.png'), pygame.image.load('R2.png'), pygame.image.load('R3.png'), pygame.image.load('R4.png'), pygame.image.load('R5.png'), pygame.image.load('R6.png'), pygame.image.load('R7.png'), pygame.image.load('R8.png'), pygame.image.load('R9.png')]
walkLeft = []pygame.image.load('L1.png'), pygame.image.load('L2.png'), pygame.image.load('L3.png'), pygame.image.load('L4.png'), pygame.image.load('L5.png'), pygame.image.load('L6.png'), pygame.image.load('L7.png'), pygame.image.load('L8.png'), pygame.image.load('L9.png')]
bg = pygame.image.load('bg.jpg')
char = pygame.image.load('standing.png')
clock = pygame.time.Clock()
bulletSound = pygame.mixer.Sound('bullet.wav')
hitSound = pygame.mixer.Sound('hit.wav')
music = pygame.mixer.music.load('music.mp3')
pygame.mixer.music.play(-1)
score = 0
class player(object):
def __init__(self,x,y,width,height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 5
self.isJump = False
self.left = False
self.right = False
self.walkCount = 0
self.jumpCount = 10
self.standing = True
self.hitbox = (self.x + 17, self.y + 11, 29, 52)
def draw(self, win):
if self.walkCount + 1 >= 27:
self.walkCount = 0
if not(self.standing):
if self.left:
win.blit(walkLeft[]self.walkCount//3], (self.x,self.y))
self.walkCount += 1
elif self.right:
win.blit(walkRight[]self.walkCount//3], (self.x,self.y))
self.walkCount +=1
else:
if self.right:
win.blit(walkRight[]0], (self.x, self.y))
else:
win.blit(walkLeft[]0], (self.x, self.y))
self.hitbox = (self.x + 17, self.y + 11, 29, 52)
#pygame.draw.rect(win, (255,0,0), self.hitbox,2)
def hit(self):
self.x = 60
self.y = 410
self.walkCount = 0
font1 = pygame.font.SysFont('comicsans', 100)
text = font1.render('-5', 1, (255,0,0))
win.blit(text, (250 - (text.get_width()/2),200))
pygame.display.update()
i = 0
while i < 300:
pygame.time.delay(10)
i += 1
for event in pygame.event.get():
if event.type == pygame.QUIT:
i = 301
pygame.quit
class projectile(object):
def __init__(self,x,y,radius,color,facing):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.facing = facing
self.vel = 8 * facing
def draw(self,win):
pygame.draw.circle(win, self.color, (self.x,self.y), self.radius)
class enemy(object):
walkRight = []pygame.image.load('R1E.png'), pygame.image.load('R2E.png'), pygame.image.load('R3E.png'), pygame.image.load('R4E.png'), pygame.image.load('R5E.png'), pygame.image.load('R6E.png'), pygame.image.load('R7E.png'), pygame.image.load('R8E.png'), pygame.image.load('R9E.png'), pygame.image.load('R10E.png'), pygame.image.load('R11E.png')]
walkLeft = []pygame.image.load('L1E.png'), pygame.image.load('L2E.png'), pygame.image.load('L3E.png'), pygame.image.load('L4E.png'), pygame.image.load('L5E.png'), pygame.image.load('L6E.png'), pygame.image.load('L7E.png'), pygame.image.load('L8E.png'), pygame.image.load('L9E.png'), pygame.image.load('L10E.png'), pygame.image.load('L11E.png')]
def __init__(self, x, y, width, height, end):
self.x = x
self.y = y
self.width = width
self.height = height
self.end = end
self.path = []self.x, self.end]
self.walkCount = 0
self.vel = 3
self.hitbox = (self.x + 17, self.y + 2, 31, 57)
self.health = 10
self.visible = True
def draw(self,win):
self.move()
if self.visible:
if self.walkCount + 1 >= 33:
self.walkCount = 0
if self.vel > 0:
win.blit(self.walkRight[]self.walkCount //3], (self.x, self.y))
self.walkCount += 1
else:
win.blit(self.walkLeft[]self.walkCount //3], (self.x, self.y))
self.walkCount += 1
pygame.draw.rect(win, (255,0,0), (self.hitbox[]0], self.hitbox[]1] - 20, 50, 10))
pygame.draw.rect(win, (0,128,0), (self.hitbox[]0], self.hitbox[]1] - 20, 50 - (5 * (10 - self.health)), 10))
self.hitbox = (self.x + 17, self.y + 2, 31, 57)
#pygame.draw.rect(win, (255,0,0), self.hitbox,2)
def move(self):
if self.vel > 0:
if self.x + self.vel < self.path[]1]:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount = 0
else:
if self.x - self.vel > self.path[]0]:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount = 0
def hit(self):
if self.health > 0:
self.health -= 1
else:
self.visible = False
print('hit')
def redrawGameWindow():
win.blit(bg, (0,0))
text = font.render('Score: ' + str(score), 1, (0,0,0))
win.blit(text, (390, 10))
man.draw(win)
goblin.draw(win)
for bullet in bullets:
bullet.draw(win)
pygame.display.update()
#mainloop
font = pygame.font.SysFont('comicsans', 30, True)
man = player(200, 410, 64,64)
goblin = enemy(100, 410, 64, 64, 450)
shootLoop = 0
bullets = []
run = True
while run:
clock.tick(27)
if man.hitbox[]1] < goblin.hitbox[]1] + goblin.hitbox[]3] and man.hitbox[]1] + man.hitbox[]3] > goblin.hitbox[]1]:
if man.hitbox[]0] + man.hitbox[]2] > goblin.hitbox[]0] and man.hitbox[]0] < goblin.hitbox[]0] + goblin.hitbox[]2]:
man.hit()
score -= 5
if shootLoop > 0:
shootLoop += 1
if shootLoop > 3:
shootLoop = 0
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for bullet in bullets:
if bullet.y - bullet.radius < goblin.hitbox[]1] + goblin.hitbox[]3] and bullet.y + bullet.radius > goblin.hitbox[]1]:
if bullet.x + bullet.radius > goblin.hitbox[]0] and bullet.x - bullet.radius < goblin.hitbox[]0] + goblin.hitbox[]2]:
hitSound.play()
goblin.hit()
score += 1
bullets.pop(bullets.index(bullet))
if bullet.x < 500 and bullet.x > 0:
bullet.x += bullet.vel
else:
bullets.pop(bullets.index(bullet))
keys = pygame.key.get_pressed()
if keys[]pygame.K_SPACE] and shootLoop == 0:
bulletSound.play()
if man.left:
facing = -1
else:
facing = 1
if len(bullets) < 5:
bullets.append(projectile(round(man.x + man.width //2), round(man.y + man.height//2), 6, (0,0,0), facing))
shootLoop = 1
if keys[]pygame.K_LEFT] and man.x > man.vel:
man.x -= man.vel
man.left = True
man.right = False
man.standing = False
elif keys[]pygame.K_RIGHT] and man.x < 500 - man.width - man.vel:
man.x += man.vel
man.right = True
man.left = False
man.standing = False
else:
man.standing = True
man.walkCount = 0
if not(man.isJump):
if keys[]pygame.K_UP]:
man.isJump = True
man.right = False
man.left = False
man.walkCount = 0
else:
if man.jumpCount >= -10:
neg = 1
if man.jumpCount < 0:
neg = -1
man.y -= (man.jumpCount ** 2) * 0.5 * neg
man.jumpCount -= 1
else:
man.isJump = False
man.jumpCount = 10
redrawGameWindow()
pygame.quit
Finishing Touches
youtube
In this tutorial I fix a few bugs that came up throughout the way.
If you’d like to see which bugs I fix and how please watch the video.
Otherwise the modified code is down below.
Next Steps
Congratulations on making it this far and completing the tutorial series! I hope you learned a lot and are ready to move on to making more advanced games with python!Some other tutorials you may enjoy are featured below.– Snake Game Tutorial – Tetris Game Tutorial – Side-Scrolling Game TutorialIf my content has helped you out please consider supporting the website by donating! Any amount is greatly appreciated and your support is what keeps this site running.
Full Code
import pygame
pygame.init()
win = pygame.display.set_mode((500,480))
pygame.display.set_caption("First Game")
walkRight = [pygame.image.load('R1.png'), pygame.image.load('R2.png'), pygame.image.load('R3.png'), pygame.image.load('R4.png'), pygame.image.load('R5.png'), pygame.image.load('R6.png'), pygame.image.load('R7.png'), pygame.image.load('R8.png'), pygame.image.load('R9.png')]
walkLeft = [pygame.image.load('L1.png'), pygame.image.load('L2.png'), pygame.image.load('L3.png'), pygame.image.load('L4.png'), pygame.image.load('L5.png'), pygame.image.load('L6.png'), pygame.image.load('L7.png'), pygame.image.load('L8.png'), pygame.image.load('L9.png')]
bg = pygame.image.load('bg.jpg')
char = pygame.image.load('standing.png')
clock = pygame.time.Clock()
bulletSound = pygame.mixer.Sound('bullet.wav')
hitSound = pygame.mixer.Sound('hit.wav')
music = pygame.mixer.music.load('music.mp3')
pygame.mixer.music.play(-1)
score = 0
class player(object):
def __init__(self,x,y,width,height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 5
self.isJump = False
self.left = False
self.right = False
self.walkCount = 0
self.jumpCount = 10
self.standing = True
self.hitbox = (self.x + 17, self.y + 11, 29, 52)
def draw(self, win):
if self.walkCount + 1 >= 27:
self.walkCount = 0
if not(self.standing):
if self.left:
win.blit(walkLeft[self.walkCount//3], (self.x,self.y))
self.walkCount += 1
elif self.right:
win.blit(walkRight[self.walkCount//3], (self.x,self.y))
self.walkCount +=1
else:
if self.right:
win.blit(walkRight[0], (self.x, self.y))
else:
win.blit(walkLeft[0], (self.x, self.y))
self.hitbox = (self.x + 17, self.y + 11, 29, 52)
#pygame.draw.rect(win, (255,0,0), self.hitbox,2)
def hit(self):
self.isJump = False
self.jumpCount = 10
self.x = 100
self.y = 410
self.walkCount = 0
font1 = pygame.font.SysFont('comicsans', 100)
text = font1.render('-5', 1, (255,0,0))
win.blit(text, (250 - (text.get_width()/2),200))
pygame.display.update()
i = 0
while i < 200:
pygame.time.delay(10)
i += 1
for event in pygame.event.get():
if event.type == pygame.QUIT:
i = 201
pygame.quit()
class projectile(object):
def __init__(self,x,y,radius,color,facing):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.facing = facing
self.vel = 8 * facing
def draw(self,win):
pygame.draw.circle(win, self.color, (self.x,self.y), self.radius)
class enemy(object):
walkRight = [pygame.image.load('R1E.png'), pygame.image.load('R2E.png'), pygame.image.load('R3E.png'), pygame.image.load('R4E.png'), pygame.image.load('R5E.png'), pygame.image.load('R6E.png'), pygame.image.load('R7E.png'), pygame.image.load('R8E.png'), pygame.image.load('R9E.png'), pygame.image.load('R10E.png'), pygame.image.load('R11E.png')]
walkLeft = [pygame.image.load('L1E.png'), pygame.image.load('L2E.png'), pygame.image.load('L3E.png'), pygame.image.load('L4E.png'), pygame.image.load('L5E.png'), pygame.image.load('L6E.png'), pygame.image.load('L7E.png'), pygame.image.load('L8E.png'), pygame.image.load('L9E.png'), pygame.image.load('L10E.png'), pygame.image.load('L11E.png')]
def __init__(self, x, y, width, height, end):
self.x = x
self.y = y
self.width = width
self.height = height
self.end = end
self.path = [self.x, self.end]
self.walkCount = 0
self.vel = 3
self.hitbox = (self.x + 17, self.y + 2, 31, 57)
self.health = 10
self.visible = True
def draw(self,win):
self.move()
if self.visible:
if self.walkCount + 1 >= 33:
self.walkCount = 0
if self.vel > 0:
win.blit(self.walkRight[self.walkCount //3], (self.x, self.y))
self.walkCount += 1
else:
win.blit(self.walkLeft[self.walkCount //3], (self.x, self.y))
self.walkCount += 1
pygame.draw.rect(win, (255,0,0), (self.hitbox[0], self.hitbox[1] - 20, 50, 10))
pygame.draw.rect(win, (0,128,0), (self.hitbox[0], self.hitbox[1] - 20, 50 - (5 * (10 - self.health)), 10))
self.hitbox = (self.x + 17, self.y + 2, 31, 57)
#pygame.draw.rect(win, (255,0,0), self.hitbox,2)
def move(self):
if self.vel > 0:
if self.x + self.vel < self.path[1]:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount = 0
else:
if self.x - self.vel > self.path[0]:
self.x += self.vel
else:
self.vel = self.vel * -1
self.walkCount = 0
def hit(self):
if self.health > 0:
self.health -= 1
else:
self.visible = False
print('hit')
def redrawGameWindow():
win.blit(bg, (0,0))
text = font.render('Score: ' + str(score), 1, (0,0,0))
win.blit(text, (350, 10))
man.draw(win)
goblin.draw(win)
for bullet in bullets:
bullet.draw(win)
pygame.display.update()
#mainloop
font = pygame.font.SysFont('comicsans', 30, True)
man = player(200, 410, 64,64)
goblin = enemy(100, 410, 64, 64, 450)
shootLoop = 0
bullets = []
run = True
while run:
clock.tick(27)
if goblin.visible == True:
if man.hitbox[1] < goblin.hitbox[1] + goblin.hitbox[3] and man.hitbox[1] + man.hitbox[3] > goblin.hitbox[1]:
if man.hitbox[0] + man.hitbox[2] > goblin.hitbox[0] and man.hitbox[0] < goblin.hitbox[0] + goblin.hitbox[2]:
man.hit()
score -= 5
if shootLoop > 0:
shootLoop += 1
if shootLoop > 3:
shootLoop = 0
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for bullet in bullets:
if bullet.y - bullet.radius < goblin.hitbox[1] + goblin.hitbox[3] and bullet.y + bullet.radius > goblin.hitbox[1]:
if bullet.x + bullet.radius > goblin.hitbox[0] and bullet.x - bullet.radius < goblin.hitbox[0] + goblin.hitbox[2]:
hitSound.play()
goblin.hit()
score += 1
bullets.pop(bullets.index(bullet))
if bullet.x < 500 and bullet.x > 0:
bullet.x += bullet.vel
else:
bullets.pop(bullets.index(bullet))
keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE] and shootLoop == 0:
bulletSound.play()
if man.left:
facing = -1
else:
facing = 1
if len(bullets) < 5:
bullets.append(projectile(round(man.x + man.width //2), round(man.y + man.height//2), 6, (0,0,0), facing))
shootLoop = 1
if keys[pygame.K_LEFT] and man.x > man.vel:
man.x -= man.vel
man.left = True
man.right = False
man.standing = False
elif keys[pygame.K_RIGHT] and man.x < 500 - man.width - man.vel:
man.x += man.vel
man.right = True
man.left = False
man.standing = False
else:
man.standing = True
man.walkCount = 0
if not(man.isJump):
if keys[pygame.K_UP]:
man.isJump = True
man.right = False
man.left = False
man.walkCount = 0
else:
if man.jumpCount >= -10:
neg = 1
if man.jumpCount < 0:
neg = -1
man.y -= (man.jumpCount ** 2) * 0.5 * neg
man.jumpCount -= 1
else:
man.isJump = False
man.jumpCount = 10
redrawGameWindow()
pygame.quit()
pygame.time
pygame module for monitoring time
pygame.time.get_ticks |
— |
get the time in milliseconds |
pygame.time.wait |
— |
pause the program for an amount of time |
pygame.time.delay |
— |
pause the program for an amount of time |
pygame.time.set_timer |
— |
repeatedly create an event on the event queue |
pygame.time.Clock |
— |
create an object to help track time |
Times in pygame are represented in milliseconds (1/1000 seconds).
Most platforms have a limited time resolution of around 10 milliseconds.
This resolution, in milliseconds, is given in the TIMER_RESOLUTION constant.
pygame.time.get_ticks()
get the time in milliseconds
get_ticks() -> milliseconds
Return the number of milliseconds since pygame.init() was called.
Before pygame is initialized this will always be 0.
pygame.time.wait()
pause the program for an amount of time
wait(milliseconds) -> time
Will pause for a given number of milliseconds.
This function sleeps the process to share the processor with other programs.
A program that waits for even a few milliseconds will consume very little processor time.
It is slightly less accurate than the pygame.time.delay() function.
This returns the actual number of milliseconds used.
pygame.time.delay()
pause the program for an amount of time
delay(milliseconds) -> time
Will pause for a given number of milliseconds.
This function will use the processor (rather than sleeping) in order to make the delay more accurate than pygame.time.wait().
This returns the actual number of milliseconds used.
pygame.time.set_timer()
repeatedly create an event on the event queue
set_timer(eventid, milliseconds) -> None
set_timer(eventid, milliseconds, once) -> None
Set an event type to appear on the event queue every given number of milliseconds.
The first event will not appear until the amount of time has passed.
Every event type can have a separate timer attached to it.
It is best to use
the value between pygame.USEREVENT and pygame.NUMEVENTS.
To disable the timer for an event, set the milliseconds argument to 0.
If the once argument is True, then only send the timer once.
New in pygame 2.0.0.dev3: once argument added.
pygame.time.Clock
create an object to help track time
Clock() -> Clock
pygame.time.Clock.tick |
— |
update the clock |
pygame.time.Clock.tick_busy_loop |
— |
update the clock |
>pygame.time.Clock.get_time |
— |
time used in the previous tick |
pygame.time.Clock.get_rawtime |
— |
actual time used in the previous tick |
pygame.time.Clock.get_fps |
— |
compute the clock framerate |
Creates a new Clock object that can be used to track an amount of time.
The
clock also provides several functions to help control a game's framerate.
tick()
update the clock
tick(framerate=0) -> milliseconds
:sg:` -> `
This method should be called once per frame.
It will compute how many milliseconds have passed since the previous call.
If you pass the optional framerate argument the function will delay to keep the game running slower than the given ticks per second.
This can be used to help limit the runtime speed of a game.
By calling Clock.tick(40) once per frame, the program will never run at more than 40 frames per second.
Note that this function uses SDL_Delay function which is not accurate on every platform, but does not use much CPU.
Use tick_busy_loop if you want an accurate timer, and don't mind chewing CPU.
tick_busy_loop()
update the clock
tick_busy_loop(framerate=0) -> milliseconds
:sg:` -> `
This method should be called once per frame.
It will compute how many milliseconds have passed since the previous call.
If you pass the optional framerate argument the function will delay to keep the game running slower than the given ticks per second.
This can be used to help limit the runtime speed of a game.
By calling Clock.tick_busy_loop(40) once per frame, the program will never run at more than 40 frames per second.
Note that this function uses pygame.time.delay() pause the program for an amount of time, which uses lots of CPU in a busy loop to make sure that timing is more accurate.
New in pygame 1.8.
get_time()
time used in the previous tick
get_time() -> milliseconds
The number of milliseconds that passed between the previous two calls to Clock.tick().
get_rawtime()
actual time used in the previous tick
get_rawtime() -> milliseconds
Similar to Clock.get_time(), but does not include any time used while Clock.tick() was delaying to limit the framerate.
get_fps()
compute the clock framerate
get_fps() -> float
Compute your game's framerate (in frames per second).
It is computed by averaging the last ten calls to Clock.tick().
Act of creation
To work with PyGame we must initialize it first, for this job PyGame have pygame module:
import pygame
from pygame.locals import *
pygame.init
pygame.quit
To initialize every part of PyGame just call pygame.init() to quit your app pygame.quit()
pygame.get_error
pygame.error
To watch current error use pygame.get_error() but this string is also given to you when pygame.error exception rise.
pygame.error is standard PyGame exception raised when any error accrue.
pygame.register_quit
If we need to clean up something before closing PyGame create for it a function and then call pygame.register_quit(written_function) and leave rest for PyGame.
You can call it multiple times PyGame will call all of them in reversed order.
(First will be the last)
Tutorials Basic
Here you can find tutorial guiding you through process of making simple game with Python and PyGame.
Starting from scratch we will make simple yet useful game engine (backbone of every game), and few games showing by example how complete games look like.
Hope you will enjoy it!
Python
Probably best programming language out there! Chosen because of its simplicity and small learning curve.
PyGame
PyGame can handle time, video (both still images and vids), music, fonts, different image formats, cursors, mouse, keyboard, Joysticks and much much more.
And all of that is very simple.
SDL
PyGame is based on SDL.
A C library that is cross platform and also very simple.
PyGame however is not just simple wrapper for it, but also add its own features, so writing games is even simpler.
Versions used during this tutorial:
Python 2.7
PyGame 1.9.1
If you can not run tutorial examples in Python 3, pleas report it as bugs.
However there are many out there who still use Python 2, so it will be used as primary version.
Source code
Last thing before we start our codding.
Source code for this tutorial is hosted at bitbucket.com
If you want to you can clone whole repo.
Each tutorial have its own tag or multiple tags if there are more then one version of code discussed during tutorial.
If you do not know what I'm talking about, then do not worry! I will provide links to downloads of source code for and at the bottom of each tutorial.
Game loop
Before writing first lines of code I will mention about how game loop is designed in PyGame.
Game loop is the place where events, game logic and rendering onto screen is performed.
It should look similar to:
while True:
events()
loop()
render()
Where event() proceeds events like pressed keys, mouse motion etc.
loop() compute changes in the game world like NPC's moves, player moves, AI, game score.
And render() just print out on the screen graphic.
That separation of tasks makes your game design easier and allow to easy changes in code when new ideas rise in you head.
First code
Its time for more code:
import pygame
from pygame.locals import *
class App:
def on_execute(self):
pass
if __name__ == "__main__" :
theApp = App()
theApp.on_execute()
As you can see I prefer classes than functions.
That is because OOP is getting more and more fans in game programming due to it correlation with game design.
Simply in game we have objects, theirs tasks etc.
even when programing without classes.
App will be our main class.
To run game we will only need call on_execute() function.
Yes this function will contain our Game Loop.
Creating simple window
Lets add some more code:
import pygame
from pygame.locals import *
class App:
def __init__(self):
self._running = True
self._display_surf = None
self.size = self.weight, self.height = 640, 400
def on_init(self):
pygame.init()
self._display_surf = pygame.display.set_mode(self.size, pygame.HWSURFACE | pygame.DOUBLEBUF)
self._running = True
def on_event(self, event):
if event.type == pygame.QUIT:
self._running = False
def on_loop(self):
pass
def on_render(self):
pass
def on_cleanup(self):
pygame.quit()
def on_execute(self):
if self.on_init() == False:
self._running = False
while( self._running ):
for event in pygame.event.get():
self.on_event(event)
self.on_loop()
self.on_render()
self.on_cleanup()
if __name__ == "__main__" :
theApp = App()
theApp.on_execute()
Test
You should see black window.
If window don't wont to quit use 'xkill' under linux or task manager under windows.
on_init on_event on_loop on_render on_cleanup
on_init calls pygame.init() that initialize all PyGame modules.
Then it create main display - 640x400 window and try to use hardware acceleration.
At the end this routine sets _running to True.
on_event check if Quit event happened if so sets _running to False wich will break game loop.
on_loop and OnRender do nothing.
on_cleanup call pygame.quit() that quits all PyGame modules.
Anything else will be cleaned up by Python.
on_execute
on_execute initialize pygame than enter main loop in which check events and then compute and render everything till _running is "True" and only Quit event will set it to "False".
Before quitting it will cleanup.
If you will run this file as program it will create instance of App and call its on_execute().
(3 last lines)
Big thx to people who helped me to improve this tutorial by woicing their ideas in comments (which are now deleted):
Sin
fidget
Alan
aanund
Kwright
Zipped source code:
Small example.
Big example
Drawing on the screen
Images
In PyGame Images are stored as Surface.
Also display can be treated as ordinary image.
It simplify screen management.
PyGame uses odd coordinate system.
top left corner is (0,0) top right (640,0) bottom left (0,480) and bottom right is (640,480) if window is (640,480) of course values can be different and you set them when calling pygame.display.set_mode().
Drawing on the screen
To draw something on the screen we must first load image from file.
Then blit it on display surface and at the end order PyGame to draw everything on the screen.
Blitting is a process of putting one image onto another e.g.
we will put our hero on the screen every frame in a bit different positions to create illusion of motion.
display_surf.blit(image_to_paint, (0,0))
It will paint image_to_paint in (0,0) point but you can paint it everywhere inside display_surf.
Loading images
For loading images PyGame have pygame.image module.
To load a particular image use:
image_surf = pygame.image.load("myimage.bmp").convert()
This line means load "myimage.bmp" and put it in new Surface "pygame.image.load("myimage.bmp")", than take
this Surface and convert to new with best pixel&color format and assign it to image_surf.
If you wan't to make your games os independent use os.path.join("folder_where_your_data_are", "file_name_of_image") to create os specific path to file e.g uder linux it will be "folder_where_your_data_are/file_name_of_image"
Sum up
import pygame
from pygame.locals import *
class App:
def __init__(self):
self._running = True
self._display_surf = None
self._image_surf = None
def on_init(self):
pygame.init()
self._display_surf = pygame.display.set_mode((350,350), pygame.HWSURFACE)
self._running = True
self._image_surf = pygame.image.load("myimage.jpg").convert()
def on_event(self, event):
if event.type == QUIT:
self._running = False
def on_loop(self):
pass
def on_render(self):
self._display_surf.blit(self._image_surf,(0,0))
pygame.display.flip()
def on_cleanup(self):
pygame.quit()
def on_execute(self):
if self.on_init() == False:
self._running = False
while( self._running ):
for event in pygame.event.get():
self.on_event(event)
self.on_loop()
self.on_render()
self.on_cleanup()
if __name__ == "__main__" :
theApp = App()
theApp.on_execute()
I've changed only on_init and on_render.
If you like you can use this file.
Which btw show glider from Conawy's Game of Life.
Multiple images in one file
Imagine that in our game we have 100 types of ground is this mean that we have to create 100 files?
NO!! just put therm together and order PyGame to cut out only those you need at the moment.
use:
image_surf = pygame.image.load("one_big_file.bmp").convert()
# ........
# and for drawing only part of it:
display_surf.blit(image_surf, (0,0) , rect_containing_coordinates_to_draw)
Zipped source code:
Drawing example
Event handling
Events are fairly simple idea.
We want our game to perform its tasks regardless of user input.
Imagine game which force user to constantly move mouse in order to just work!
However it mean that user can interact at any given point with our game.
When we are displaying main menu, when we are painting our next frame, or computing game logic.
In order to store user input for letter use, we will (or more precisely PyGame will) put them in containers which we will call Events.
Each event store data specific to one type of event (like mouse button pressed, key down, etc), and for only one user generated input.
So if user click on multiple keys, we will get multiple events.
If user press 'B' multiple times, we will get multiple events, etc.
To enable event handling in our games we will create one class, that have python functions for every type of events, and one which will call them depending on the type of event.
Code
Save it as cevent.py.
import pygame
from pygame.locals import *
import pygame
from pygame.locals import *
class CEvent:
def __init__(self):
pass
def on_input_focus(self):
pass
def on_input_blur(self):
pass
def on_key_down(self, event):
pass
def on_key_up(self, event):
pass
def on_mouse_focus(self):
pass
def on_mouse_blur(self):
pass
def on_mouse_move(self, event):
pass
def on_mouse_wheel(self, event):
pass
def on_lbutton_up(self, event):
pass
def on_lbutton_down(self, event):
pass
def on_rbutton_up(self, event):
pass
def on_rbutton_down(self, event):
pass
def on_mbutton_up(self, event):
pass
def on_mbutton_down(self, event):
pass
def on_minimize(self):
pass
def on_restore(self):
pass
def on_resize(self,event):
pass
def on_expose(self):
pass
def on_exit(self):
pass
def on_user(self,event):
pass
def on_joy_axis(self,event):
pass
def on_joybutton_up(self,event):
pass
def on_joybutton_down(self,event):
pass
def on_joy_hat(self,event):
pass
def on_joy_ball(self,event):
pass
def on_event(self, event):
pass
if __name__ == "__main__" :
event = CEvent()
Explanation
I think code is self-explanatory.
I will say only that routines on_joy_* will be introduced in distinct tutorial if there will be demand for it.
All others will be introduced through out this tutorials.
One to rule them all
So far we have lots of methods how to chain them all ? How to make one to rule them all ;) ?
Change on_event into following:
def on_event(self, event):
if event.type == QUIT:
self.on_exit()
elif event.type >= USEREVENT:
self.on_user(event)
elif event.type == VIDEOEXPOSE:
self.on_expose()
elif event.type == VIDEORESIZE:
self.on_resize(event)
elif event.type == KEYUP:
self.on_key_up(event)
elif event.type == KEYDOWN:
self.on_key_down(event)
elif event.type == MOUSEMOTION:
self.on_mouse_move(event)
elif event.type == MOUSEBUTTONUP:
if event.button == 0:
self.on_lbutton_up(event)
elif event.button == 1:
self.on_mbutton_up(event)
elif event.button == 2:
self.on_rbutton_up(event)
elif event.type == MOUSEBUTTONDOWN:
if event.button == 0:
self.on_lbutton_down(event)
elif event.button == 1:
self.on_mbutton_down(event)
elif event.button == 2:
self.on_rbutton_down(event)
elif event.type == ACTIVEEVENT:
if event.state == 1:
if event.gain:
self.on_mouse_focus()
else:
self.on_mouse_blur()
elif event.state == 2:
if event.gain:
self.on_input_focus()
else:
self.on_input_blur()
elif event.state == 4:
if event.gain:
self.on_restore()
else:
self.on_minimize()
Usage
Now that we have laid ground work, we can use it!
CApp should inherit after CEvent, so all functions in CEvent are available in CApp.
Then we just need to change event handling functions we need to.
Here is simple example which will use our new event handling code to handle exit event:
#add in import section
import cevent
# change CApp into
class CApp(cevent.CEvent):
#...
#...
delete on_event(): !!!!!!!!!!
#add on_exit(self) to CApp:
def on_exit(self):
self._running = False
Now our code is a bit tricky! Python will not find on_event in App, so it will use it's parent's class (CEvent) method on_event.
But on_event will call on_exit from App class!
How CEvent can know about our on_exit() function in App class? I can not!
How ever we have told python that App is inheriting after CEvent, so Python will do few things for us:
will provide every method from CEvent as if they where App's own methods (just like with on_event)
will make sure that methods defined in App will be preferred over CEvent methods (just like with on_exit)
Professional programmers call this pattern "Template method".
Because if you look at what we have done, then you will realize that our on_event function is template, with places (like on_exit) that can be changed letter.
Zipped Source Code:
Full events example
PyGame.Time
Power of Time
You need time to create motion, play sound, react to events in other words to make games.
Motion is just illusion put some fancy image on the desk and move it a little every second.
Yes it is a motion.
Of course in game we will be moving a bit faster but idea is the same.
Another reason…
This is one of the easiest modules in PyGame (just after pygame module).
pygame.time
In computers generally we don't count seconds we counts ticks.
One tick can be 0.1 0.01 or 0.001 of second but those ticks are not for you! Computer counts them in special hardware variable.
You can relay only on mechanisms that provide Your operating system.
In PyGame things are a bit easier.
First you can see how much ticks have already have past, so by comparing to of them You know how much time have past.
Second You can waits for given period of time.
Next You can ask pygame.time to invoke given event after every time you wont.
And you can create clocks that can manage FPS, frame per seconds.
FPS counts how many times You have updated screen.
So pygame.time provide You with every thing You will ever need.
Time in pygame is measured in milliseconds (1/1000 of a second) but on some platforms it can be soundet to 10 milliseconds.
pygame.time.get_ticks
This function returns time that have past since pygame.init, but before invocation pygame.init it will always return 0.
pygame.time.wait
Sometimes we will need to wait certain amount of time.
pygame.time.wait will do it.
You only must be aware that during that time processor will be scheduled to other programs so You may awake after given time (but never before), if You want more precision following function is for you.
pygame.time.delay
pygame.time.delay() What's the difference? This function will use your CPU heavily, so other programs will suffer a bit.
But sometimes precision is more important than that so you can choose.
P.S.
try to use pygame.time.wait for normal game or programs, you can always change to pygame.time.delay if you will need optimizations.
pygame.time.set_timer
Another common situation in games (and multimedia apps) is when you must repeat one thing every given period of time e.g.
in tetris blocks fall down every second, your hero heal 1HP every second etc.
In pygame you will do it by calling pygame.time.set_timer(eventid, milliseconds).
It will invoke event every milliseconds you specified.
This event will be available in event queue.
What is important you can call this function multiple times with different eventid to have multiple events.
When you don't need a particular event to ocure any more just call pygame.time.set_time(eventid_you_want_to_stop, 0).
pygame.time.Clock
This is class that will help us to track amount of time or to manage framerate.
pygame.time.Clock.tick
To track how many frames we have rendered we must call pygame.time.Clock.tick() after computing and rendering graphic on the screen, also calling pygame.time.Clock.tick( number) will order pygame to render only number times per second, so if your rendering takes 10 milliseconds it would normally be rendered 100 times per second but calling pygame.time.Clock.tick(60) will cause rendering only 60 times and then pygame will automaticly call pygame.time.wait().
If you remember it is not so good if you need great precision then following function is better:
pygame.time.Clock.tick_busy_loop
pygame.time.Clock.tick_busy_loop do the same as pygame.time.Clock.tick but uses pygame.time.delay so use heavily CPU but is very precise.
pygame.time.Clock.get_time
When you need time that have passed from last pygame.time.Clock.tick just call pygame.time.Clock.get_time().
You will get time in milliseconds.
pygame.time.Clock.get_rawtime
pygame.time.Clock.get_rawtime is very similar but it doesn't count time spended by pygame.time.Clock.tick() on pygame.time.deley.
pygame.time.Clock.get_fps
Last function in pygame.time is pygame.time.Clock.get_fps that returns current FPS number.
Setting up programing environment
Journey
OK.
To start journey with PyGame we will need Python installed.
See http://python.org for Python 2.5 because 2.6 or 3.0 may not work :(
I will not show you how to install Python (because it is so simple as "Next -> Next -> Next …").
Installing PyGame
To get PyGame visit http://pygame.org and get the latest PyGame.
In Part Two we will discuss optional PyGame modules that requires some additional libraries but don't worry.
I will show you how to install them when I will be talking about them.
Linux
Just look at your repos python and pygame are there and waits for you ;)
Now check if PyGame is working
Open python interactive shell and write:
import pygame
print pygame.version
output(may be different number):
1.8.1
Version 1.8.1 is latest when I'm writing this words.
Sprites
Image + Rect = Sprite
PyGame offer a pygame.Sprite module that contain multiple classes.
The simplest are: Sprite and Group.
Sprite is basicly objesct that have its image and rect, and also can draw itself on surface.
Groupe is an object that groups multiple sprites and can print them all.
Sprite
When inheriting this class you will mostly wont to change Sprite.update() metchod, initialize Sprite.image and Sprite.rect and also don't forget to initialize super class before adding sprite to a Group object.
pygeme.sprite.Sprite
pygame.sprite.Sprite(*groups) returns new Sprite
pygeme.sprite.Sprite.update
Sprite.update(*args) do nothing.
Is here only to be implemented in your classes.
Is called when Group.upded() is called.
pygeme.sprite.Sprite.add
Sprite.add(*groups) adds this sprite to given groups.
pygeme.sprite.Sprite.remove
Sprite.remove(*groups) if sprite is a member of those groups it will be removed from them.
pygeme.sprite.Sprite.kill
Sprite.kill() sprite will be removed from all groups.
pygeme.sprite.Sprite.alive
Sprite.alive() return True if sprite is contained by any group.
pygeme.sprite.Sprite.groups
Sprite.groups() return list of all groups that contain this sprite.
DirtySprite
pygeme.sprite.DirtySprite
DirtySprite is a Sprite with some extra attributes:
dirty = 1 - 0 means no repainting, 1 repaint and set to 0, 2 repaint every frame
blendmode = 0 - used for blit, blendmodes
source_rect = None - source rect to use that it is relative to topleft (0,0) of self.image
visible = 1 - normally 1, if set to 0 it will not be repainted (you must set it dirty too to be erased from screen)
layer = 0 (READONLY value, it is read when adding it to the LayeredRenderGroup, for details see doc of LayeredRenderGroup)
Group
pygeme.sprite.Group
pygame.sprite.Group(*sprites) is a simple container available for inheriting to create more specific behavior.
Constructor will take any number of Sprites.
The group supports the following standard Python operations:
in test if a Sprite is contained
len the number of Sprites contained
bool test if any Sprites are contained
iter iterate through all the Sprites
pygeme.sprite.Group.sprites
Group.sprites() return all contained sprites.
pygeme.sprite.Group.copy
Group.copy() make identical Group object with the same sprites, it also work well with subclasses if theirs constructors takes the same arguments.
pygeme.sprite.Group.add
Group.add(*any_number_of_sprites) adds sprites to the group(also iterators are allowed)
pygeme.sprite.Group.remove
Group.remove(*any_number_of_sprites) remove sprites that are in group(also iterators are allowed)
pygeme.sprite.Group.has
Group.has(*any_number_of_sprites) test if all given sprites are in group.
To test only one sprite use:
if sprite in group:
pygeme.sprite.Group.update
Group.update(*args) call every sprite's update method and pass to them args.
pygeme.sprite.Group.draw
Group.draw(surface) draw all contained sprites into surface
pygeme.sprite.Group.clear
Group.clear(surface, color) draw all contained sprites with color into surface.
pygeme.sprite.Group.empty
Group.empty() remove all sprites from this group.
Surface an object that contain graphic
Surface represents images
Surface contain images but also have dozens (44) routines to match your needs.
Common way to create surface is to load image from file.
It will give us a new surface but with resolution and color depth as in file.
Usually it wont be a optimized format (current) so PyGame will have to convert it every time when drawing it on the screen.
To convert surface to best format use:
optimized_surf = allready_loadaed_surf.convert()
pygame.Surface
Another options are:
pygame.Surface((width, height), flags=0, depth=0, masks=None)
pygame.Surface((width, height), flags=0, Surface)
Only (width, height) are obligatory.
If will set only them PyGame will create surface with best (current) color depth and flags.
Also PyGame will fill it up with "black" color.
pygame.Surface.blit
To put one image onto another PyGame have destiny_surf.blit(source_surf, rect, source_rect, specialflags) it will return rectangle of affected pixel, copy source_rect part of source_surf onto rect part of destiny_surf.
Only source_surf is obligatory.
special flags are:
BLEND_ADD, BLEND_SUB, BLEND_MULT, BLEND_MIN, BLEND_MAX
BLEND_RGBA_ADD, BLEND_RGBA_SUB, BLEND_RGBA_MULT, BLEND_RGBA_MIN, BLEND_RGBA_MAX
BLEND_RGB_ADD, BLEND_RGB_SUB, BLEND_RGB_MULT, BLEND_RGB_MIN, BLEND_RGB_MAX
pygame.Surface.convert
To convert surface to specific format use pygame.surface.convert(surface) or convert(depth) or convert(mask) or just convert().
The last one will convert to the fastest format currently used.
Others will change to match given surface, depth, mask.
pygame.Surface.convert_alpha
Some images are made for alpha bliting for blending one transparent on opaque one.
To optimize surface for this purpose call pygame.surface.convert_apha(surface).
If won't to optimize for bliting on display call without any parameters.
pygame.Surface.copy
pygame.Surface.copy(surface) will duplicate surface.
pygame.Surface.fill
pygame.Surface.fill(color, rect=None, special_flags) will fill surface with color.
Color can be RGB or RGBA but Alpha is used only when surface have per pixel alpha.
Flags are:
BLEND_ADD, BLEND_SUB, BLEND_MULT, BLEND_MIN, BLEND_MAX
BLEND_RGBA_ADD, BLEND_RGBA_SUB, BLEND_RGBA_MULT, BLEND_RGBA_MIN, BLEND_RGBA_MAX
BLEND_RGB_ADD, BLEND_RGB_SUB, BLEND_RGB_MULT, BLEND_RGB_MIN, BLEND_RGB_MAX
pygame.Surface.set_colorkey
2D pictures always are square.
How to make parts of them transparent? Use colorkey.
Color key says PyGame to not display it.
Every picture can have its own color key, but try to use one similar to pink.
pygame.Surface.set_colorkey(color, flags=None) where color can be either RGB or RGBA.
If none colorkey will be unset.
flags can be pygame.RLEACCEL witch improve speed of bliting on non accelerated hardware but slows down time of modification.
Be aware that color key work only with full surface alpha but not with per pixel alpha
pygame.Surface.get_colorkey
pygame.Surface.get_colorkey() returns RGB value or None.
pygame.Surface.set_alpha
pygame.Surface.set_alpha(color, flag=None) sets alpha for whole surface or disable it if None passed.
Flag can be pygame.RLEACCEL
pygame.Surface.get_alpha
pygame.Surface.get_alpha() returns current surface alpha or None is disabled.
pygame.Surface.must_lock
To access pixel data in hardware accelerated surface you must get lock.
To check if needed use pygame.Surface.must_lock() but simpler and faster will be to lock all surfaces.
pygame.Surface.lock
pygame.Surface.lock will lock surface for pixel data editing.
But do it as fast as you can because this surface can't be displayed or managed by PyGame.
Also all PyGame functions use it internally but if you will call them many times in a raw try to wrap that block in lock/unlock pair to gain some performance.
pygame.Surface.unlock
Use pygame.Surface.unlock() to unlock Surface.
You can nest this two routines it is 100% safe, only final unlock will unlock surf.
pygame.Surface.get_lock
pygame.Surface.get_lock() returns True if surface is locked.
pygame.Surface.get_locks
pygame.Surface.get_locks returns tuple with all locks on current surface.
pygame.Surface.get_at
PyGame can edit single pixels is surface.
pygame.Surface.get((x,y)) returns Color of wanted pixel.
pygame.Surface.set_at
pygame.Surface.set_at((x,y),Color) sets Color on given pixel
pygame.Surface.map_rgb
pygame.Surface.map_rgb(Color) returns integer with Color mapped into integer
pygame.Surface.unmap_rgb
pygame.Surface.unmap_rgb(integer) map integer into Color.
pygame.Surface.set_clip
Surface have its own clipping area - part that can be modified.
pygame.Surface.set_cip(rect) set it to rect if None passed whole Surf is editable.
pygame.Surface.get_clip
pygame.Surface.get_clip() returns clipping rect.
pygame.Surface.subsurface
pygame.Surface.subsurface(rect) creates form rect part of Surface a new one that share pixel date with surface and is considered as child of Surface.
One Surface can have multiple subsurfaces and subsubsurfaces, also display surface can have subsurfaces if not hardware mode is set.
Pixel data are common also child inherit alpha, palette, kolorkey but others are separate e.g.
clipping rect.
pygame.Surface.get_parent
pygame.Surface.get_parent() finds parent of current surface.
If not exist none will be returned.
pygame.Surface.get_abs_parent
pygame.Surface.get_abs_parent() also finds parent but if none exist current surf will be returned.
pygame.Surface.get_offset
pygame.Surface.get_offset returns (x,y) witch are offset position of current surface inside of its parent.
(0,0) means no parent.
pygame.Surface.get_abs_offset
pygame.Surface.get_abs_offset() will return offset position but inside of top level parent of surface.
(0,0) mean no parent.
pygame.Surface.get_size
pygame.Surface.get_size() returns (width, height)
pygame.Surface.get_width
pygame.Surface.get_width() returns width
pygame.Surface.height
pygame.Surface.get_height() returns height
pygame.Surface.get_rect
pygame.Surface.get_rect(**kwargs): returns Rect that starts at (0,0) and cover all surface.
You can pass key arguments the same as names of rect data so get_rect will fill them with given values.
pygame.Surface.get_bitsize
pygame.Surface.get_bitsize() returns number of bits per pixel
pygame.Surface.get_bytesize
pygame.Surface.get_bytesize() returns number of bytes per pixel
pygame.Surface.get_flags
pygame.Surface.get_flags() returns flags:
Here is a more complete list of flags.
A full list can be found in SDL_video.h
SWSURFACE 0x00000000 # Surface is in system memory
HWSURFACE 0x00000001 # Surface is in video memory
ASYNCBLIT 0x00000004 # Use asynchronous blits if possible
Available for pygame.display.set_mode - initialize a window or screen for display
ANYFORMAT 0x10000000 # Allow any video depth/pixel-format
HWPALETTE 0x20000000 # Surface has exclusive palette
DOUBLEBUF 0x40000000 # Set up double-buffered video mode
FULLSCREEN 0x80000000 # Surface is a full screen display
OPENGL 0x00000002 # Create an OpenGL rendering context
OPENGLBLIT 0x0000000A # Create an OpenGL rendering context
# and use it for blitting.
Obsolete.
RESIZABLE 0x00000010 # This video mode may be resized
NOFRAME 0x00000020 # No window caption or edge frame
Used internally (read-only)
HWACCEL 0x00000100 # Blit uses hardware acceleration
SRCCOLORKEY 0x00001000 # Blit uses a source color key
RLEACCELOK 0x00002000 # Private flag
RLEACCEL 0x00004000 # Surface is RLE encoded
SRCALPHA 0x00010000 # Blit uses source alpha blending
PREALLOC 0x01000000 # Surface uses preallocated memory
pygame.Surface.get_pitch
pygame.Surface.get_pitch() returns bytes per Surface row
pygame.Surface.get_bounding_rect
pygame.Surface.get_bounding_rect(alpha_value=1) will return smallest rest that covers all pixels that have alpha values bigger then alpha_value
pygame.Surface.get_buffer
pygame.Surface.get_buffer() returns BufferProxy witch can be used to manipulate pixel data but it will lock surf until BufferProxy exists
Transforming surfaces
Playing with surfaces
PyGame have many function that can rotate, scale, or other fancy transformations.
They are grouped in pygame.transform module.
If have trouble with them just look at wikipedia.
pygame.transform.flip
pygame.transform.flip(Surface, xbool, ybool) flip vertically and horizontally
pygame.transform.scale
pygame.transform.scale(Surface, (width, height), DestSurface = None) resize to new resolution
pygame.transform.rotate
pygame.transform.rotate(Surface, angle) rotate an image
pygame.transform.rotozoom
pygame.transform.rotozoom(Surface, angle, scale) filtered scale and rotation
pygame.transform.scale2x
pygame.transform.scale2x(Surface, DestSurface = None) specialized image doubler
pygame.transform.smoothscale
pygame.transform.smoothscale(Surface, (width, height), DestSurface = None) scale a surface to an arbitrary size smoothly
pygame.transform.chop
pygame.transform.chop(Surface, rect) gets a copy of an image with an interior area removed
pygame.transform.laplacian
pygame.transform.laplacian(Surface, DestSurface = None) find edges in a surface
pygame.transform.average_surfaces
pygame.transform.average_surfaces(Surfaces, DestSurface = None) find the average surface from many surfaces
pygame.transform.threshold
pygame.transform.threshold(DestSurface, Surface, color, threshold = (0,0,0,0), diff_color = (0,0,0,0), change_return = True, Surface =None) finds which, and how many pixels in a surface are within a threshold of a color.
Why rectangle is fundamental ?
Rectangles are very important in game development.
As we will see later all heroes, players, monster or other thing moving in your games will be just images (or 3D objects in 3D graphic) with some parts transparent.
So why not represent their position in game world by squares.
There is another advantage that is very important.
Some objects on the screen have really hard shapes to describe.
So how we will move them or have we will detect collisions with them ? Yes we can use per pixel detection but it is precious time consuming.
And if you have 10 000 of them in game ?!? Rectangles are better because they have easy, simple and fast mathematical methods to detect collisions and sometimes you need only roughly precision.
However if you still need per pixel detection don't worry just do general rect detection and if there is any do for them per pixel detection.
So from 10 000 possible collisions (and 10 000 fast operation) you will get 10 possible (and only 10 slow operations).
Big optimization don't you think?
pygame.Rect have several attributes:
top, left, bottom, right
topleft, bottomleft, topright, bottomright
midtop, midleft, midbottom, midright
center, centerx, centery
size, width, height
w,h
You can change every of them.
But changing size, width or height will change automatically others.
pygame.Rect.move
pygame.Rect.move_ip
Games without motion are boring.
But how to update ours rectangles? Simply pygame.Rect.move(x,y) will return new rectangle placed in (x,y) point.
pygame.Rect.move_ip(x,y) behaves the same but it will change current Rect.
pygame.Rect.inflate
pygame.Rect.inflate_ip
If wont to produce new Rect with changed height and weight (but centered at the same point as current Rect) use pygame.Rect.inflate(x,y) where x and y are offsets that height and weight will be changed by.
Negative values will shrink Rect.
Use pygame.Rect.inflate_ip to change current Rect.
pygame.Rect.clamp
pygame.Rect.clamp_ip
Sometimes we need to move one Rect into another Rect (or get new one moved).
Rect have pygame.Rect.clamp(another_rect) function to do that (or corresponding pygame.Rect.clamp_ip)
pygame.Rect.clip
Pygame.Rect.clip(Rect) will return new Rect fitting in gived Rect or 0 if current Rect and given don't overlap.
pygame.Rect.union
pygame.Rect.union_ip
pygame.Rect.unionall
pygame.Rect.unionall_ip
Pygame.Rect.union(Rect) will have opposite effect.
It will return Rect that contain current and given Rect and possibly other areas.
pygame.Rect.union_ip(Rect) will change current Rect and pygame.Rect.unionall(Sequence_of_rects) will do that for many rects.
pygame.rect.unionall_ip(sequence) will change current rect
pygame.Rect.fit
pygame.Rect.fit(Rect) create new rect that is moved and resized to fit into given Rect but aspect ratio (weight/height) is unchanged so new Rect can have weight or height smaller than given Rect.
pygame.Rect.normalize
Using functions that change Rect can sometimes cause that Rect will have negative weight or height.
Use pygame.Rect.normalize() to change them to positive values (without moving or scaling).
To test collision PyGame have multiple functions for multiple thing that we wan't to test against:
pygame.Rect.collidepoint
pygame.Rect.collidepoint(x,y) test if given pint is inside current Rect.
pygame.Rect.contain
pygame.Rect.contain(Rect) will return True if current Rect contain inside given Rect.
pygame.Rect.colliderect
pygame.Rect.colliderect(Rect) return true if any part of Rect overlap with current.
pygame.Rect.collidelist
pygame.Rect.collidelistall
pygame.Rect.collidelist(list) will return first rect that collide with current one while collidelistall(list) will return all rects that collide with current.
pygame.Rect.collidedict
pygame.Rect.collidedictall
collidedict(dictionary) and collidedictall(dictionary) behaves the same but operate on dictionary.